#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int INFint = 1e9+1;
const ll INFll = (ll)1e18+1;
const int MOD=1e9+7;

template <typename T>
struct SegmentTree {
  int n;
  vector<T> node;
  using FX = function<T(T, T)>; // T•T -> T となる関数の型
  FX fx;
  const T ex; // 単位元(RMQの時はex=INFint)

  // 元配列 v をセグメント木で表現する
  SegmentTree(vector<T> v, FX fx_, T ex_): fx(fx_), ex(ex_) {
    int sz = v.size();
    n = 1; while(n < sz) n *= 2; // 最下段のノード数は元配列のサイズ以上になる最小の 2 冪 -> これを n とおく
    node.resize(2 * n - 1, ex); // セグメント木全体で必要なノード数は 2n-1 個である

    for(int i = 0; i < sz; i++) node[i + n - 1] = v[i]; // 配列そのままが入るのはindex n-1から
    for(int i = n - 2; i >= 0; i--) node[i] = fx(node[2 * i + 1], node[2 * i + 2]); // 下の段から順に初期値を計算していく(根はnode[0])
  }
  void update(int x, T val) {
    x += n - 1; // 最下段のノードにアクセスする
    node[x] = val;
    while(x > 0) {
      x = (x - 1) / 2; // 自分の親ノードに行く
      node[x] = fx(node[2 * x + 1], node[2 * x + 2]);
    }
  }
  T query(int a, int b) { return query_sub(a, b, 0, 0, n); }
  T query_sub(int a, int b, int k, int l, int r) {
    if (r <= a || b <= l) return ex;
    if (a <= l && r <= b) return node[k];
  
    T vl = query_sub(a, b, k * 2 + 1, l, (l + r) / 2);
    T vr = query_sub(a, b, k * 2 + 2, (l + r) / 2, r);
    return fx(vl, vr);
  }
  T get(int x) { // もとの配列のx番目(0-indexed)の値を返す
    return node[x + n - 1];
  }
  void print(){ // 最下段[n-1,2n-1)のn個を出力する
    for(int i(0); i < n; i++) cout << node[i + n - 1] << " ";
    cout << endl;
  }
  void printall(){ // 全てのノードの値を出力する
    for(int i(0); i<2 * n; i++) cout << node[i] << " ";
    cout << endl;
  }
};

int main(){
  int N, Q;
  cin>>N>>Q;
  vector<int> A(N);
  for(int i(0);i<N;i++) cin>>A[i];
  SegmentTree<int> stree(A, [](int x, int y){return (x ^ y);}, 0);
  for(int i(0);i<Q;i++){
    int t, x, y;
    cin>>t>>x>>y;
    x--;
    if (t == 1){
      stree.update(x, stree.get(x) ^ y);
    } else {
      cout << stree.query(x, y) << endl;
    }
  }
  return 0;
}

// C++ program to show Segment Tree - XOR of range
#include<bits/stdc++.h>
using namespace std;
#define w(x)            int x; cin>>x; while(x--)
#define nl              "\n"
#define fr(i,t)         for(int i=0;i<t;i++)
#define fr1(i,a,b)      for(int i = a; i<b; i++)
#define frr(i,n)        for(int i = n; i>=0; i--)
#define frr1(i,a,b)     for(int i = a; i>=b; i--)
#define dbug(x)         cout<<#x<<"="<<x<<endl;
#define debug(v, n)     fr(i,n){cout<<v[i]<<" ";if(i == n-1)cout<<nl;}
#define debug1(v, a, b) cout<<#v<<nl;fr1(i,a,b){cout<<v[i]<<" ";if(i == b)cout<<nl;}
#define fast            ios_base::sync_with_stdio(false);cin.tie(NULL);cout.tie(NULL);
#define pb				push_back
#define ff 		    	first
#define ss 		    	second
#define pii				pair<int, int>
#define ll				long long int
// int                      -10**9  to 10**9        4 byte       -2**31 to +2**31    -2147483647 to +2147483648
// long long int            -10**19 to 10**19       8 byte       -2**63 to +2**63
// unsigned long long int   -0      to 10**20       8 byte          0   to +2**64
// log(10 ** 5)             16.60
// log(10 ** 6)             19.93
// log(10 ** 9)             29.89
// INT_MAX                  0x7fffffff 2147483647
const int M1  = 1000000007;
const int M2  = 998244353;
const int N = 300005;

int BIT[N];

int arr[N];

void update(int idx, int val, int n){
    while(idx<=n){
        BIT[idx] ^= val;
        idx += idx & (-idx);
    }
}

void build (int n){
    fr1(i,1,n+1){
        update(i,arr[i-1],n);
    }
}



ll getxor(int idx){
    ll sum = 0;
    while(idx){
        sum ^= BIT[idx];
        idx -= idx & (-idx);
    }
    return sum;
}

void showBIT(int n){
    cout<<"BIT content : "<<nl;
    fr1(i,1,n+1){
        cout<<BIT[i]<<" ";
    }
}
int main()
{
    int n,q,type,a,b;
    cin>>n>>q;
    fr(i,n){
        cin>>arr[i];
    }
    build(n);
    fr(i,q){
            cin>>type>>a>>b;
            if(type == 1){
                update(a, b, n);
            }
            
            else{
                cout<<(getxor(b) ^ getxor(a-1))<<nl;
            }
            
        }
}