线段树
1、单点修改与区间查询
题目:洛谷:【模板】线段树 1
最基础的模板,不做解释,上代码:
#include<iostream>
#include<cstring>
using namespace std;
typedef long long LL;
const int N = 400010;
LL w[N];
int n,m;
struct Node{
int l,r;
LL sum,add;
}tr[N];
void pushup(int u){
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u){
Node &left = tr[u << 1],&right = tr[u << 1 | 1],&root = tr[u];
if(root.add){
left.add += root.add,left.sum += (LL)(left.r - left.l + 1) * root.add;
right.add += root.add,right.sum += (LL)(right.r - right.l + 1) * root.add;
root.add = 0;
}
}
void build(int u,int l,int r){
if(l == r) tr[u] = {l,r,w[l],0};
else{
tr[u] = {l,r};
int mid = l + r >> 1;
build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
pushup(u);
}
}
void modify(int u,int l,int r,int d){
if(tr[u].l >= l && tr[u].r <= r){
tr[u].add += d;
tr[u].sum += (LL)(tr[u].r - tr[u].l + 1) * d;
}
else{
pushdown(u);
int mid = tr[u].r + tr[u].l >> 1;
if(l <= mid) modify(u << 1,l,r,d);
if(r > mid) modify(u << 1 | 1,l,r,d);
pushup(u);
}
}
LL query(int u,int l,int r){
if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
else{
LL res = 0;
int mid = tr[u].l + tr[u].r >> 1;
pushdown(u);
if(l <= mid) res += query(u << 1,l,r);
if(r > mid) res += query(u << 1 | 1,l,r);
return res;
}
}
int main(){
cin >> n >> m;
for(int i = 1;i <= n;i ++) cin >> w[i];
build(1,1,n);
while(m --){
int op;
cin >> op;
if(op == 1){
int x,y,k;
cin >> x >> y >> k;
modify(1,x,y,k);
}
else{
int x,y;
cin >> x >> y;
cout << query(1,x,y) << endl;
}
}
return 0;
}
2、区间修改/懒标记
题目:洛谷:校门外的树
该题极其适合初学者练习区间修改(虽然是大材小用)
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 10010;
int n,m;
struct Tree{
int l,r;
int sum,lazy;
}tr[4 * N];
void pushup(int u){
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void pushdown(int u){
Tree &root = tr[u],&left = tr[u << 1],&right = tr[u << 1 | 1];
if(root.lazy){
left.lazy = root.lazy,left.sum = 0;
right.lazy = root.lazy,right.sum = 0;
root.lazy = 0;
}
}
void build(int u,int l,int r){
if(l == r) tr[u] = {l,r,1,0};
else{
tr[u] = {l,r};
int mid = l + r >> 1;
build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
pushup(u);
}
}
void modify(int u,int l,int r){
if(tr[u].l >= l && tr[u].r <= r){
tr[u].lazy = 1;
tr[u].sum = 0;
}
else{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1,l,r);
if(r > mid) modify(u << 1 | 1,l,r);
pushup(u);
}
}
int main(){
cin >> n >> m;
build(1,1,n + 1);
while(m --){
int a,b;
cin >> a >> b;
modify(1,a + 1,b + 1);
}
cout << tr[1].sum;
return 0;
}
3、权值线段树
权值线段树可以维护某个区间内数组元素出现的次数
题目:洛谷:逆序对
逆序对可以用归并排序、树状数组及线段树来求,但归并排序只能求这种最基本的逆序对,一旦题目稍微变化就需要用树状数组或线段树来解决。树状数组一般比线段树更方便,这里选择线段树来解决该问题。
在此题中,线段树维护的是每个点出现的次数。由于数据范围太大且分散,我们先将输入的a数组离散化,从而使每个数有序对应1~n。将a数组从左至右插入树中,每次插入后都查询一下该数(在书中对应的位置 + 1 , n) 这个区间的数的总数,这就是这个数对应的逆序对,最后将所有的逆序对加起来即为答案。记得开long long!!!
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cstdio>
using namespace std;
typedef long long LL;
const int N = 500010;
int n;
int a[N];
vector<int> w;
struct Tree{
int l,r;
LL val;
}tr[N * 4];
void pushup(int u){
tr[u].val = tr[u << 1].val + tr[u << 1 | 1].val;
}
void build(int u,int l,int r){
if(l == r) tr[u] = {l,r,0};
else{
tr[u] = {l,r};
int mid = l + r >> 1;
build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
pushup(u);
}
}
int find(int x){
int l = 0,r = w.size() - 1;
while(l < r){
int mid = l + r >> 1;
if(w[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1;
}
void modify(int u,int x){
if(tr[u].l == tr[u].r && tr[u].l == x){
tr[u].val ++;
}
else{
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) modify(u << 1,x);
else modify(u << 1 | 1,x);
pushup(u);
}
}
LL query(int u,int l,int r){
if(tr[u].l >= l && tr[u].r <= r) return tr[u].val;
LL res = 0;
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) res += query(u << 1,l,r);
if(r > mid) res += query(u << 1 | 1,l,r);
pushup(u);
return res;
}
int main(){
cin >> n;
build(1,1,n);
for(int i = 0;i < n;i ++){
scanf("%d",&a[i]);
w.push_back(a[i]);
}
sort(w.begin(),w.end());
w.erase(unique(w.begin(),w.end()),w.end());
LL ans = 0;
for(int i = 0;i < n;i ++){
int t = find(a[i]);
modify(1,t);
if(t != n) ans += query(1,t + 1,n);
}
cout << ans;
return 0;
}
扩展:洛谷:三元上升子序列
该题是逆序对的扩展题,利用上题思想,从头到尾遍历区间,每个数的(前面比他小的数的个数 * 后面比他大的数的个数)之和即为答案,建立两个线段树,一个从头到尾存储,另一个从尾到头存储,分别计算前面比他小的数的个数和后面比他大的数的个数。
代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<cstdio>
using namespace std;
typedef long long LL;
const int N = 500010;
int n;
LL a[N],s[N],ls[N];
vector<int> w;
struct Tree{
int l,r;
LL val;
};
Tree tr2[4 * N],tr1[N * 4];
void pushup(Tree tr[],int u){
tr[u].val = tr[u << 1].val + tr[u << 1 | 1].val;
}
void build(Tree tr[],int u,int l,int r){
if(l == r) tr[u] = {l,r,0};
else{
tr[u] = {l,r};
int mid = l + r >> 1;
build(tr,u << 1,l,mid),build(tr,u << 1 | 1,mid + 1,r);
pushup(tr,u);
}
}
int find(int x){
int l = 0,r = w.size() - 1;
while(l < r){
int mid = l + r >> 1;
if(w[mid] >= x) r = mid;
else l = mid + 1;
}
return r + 1;
}
void modify(Tree tr[],int u,int x){
if(tr[u].l == tr[u].r && tr[u].l == x){
tr[u].val ++;
}
else{
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) modify(tr,u << 1,x);
else modify(tr,u << 1 | 1,x);
pushup(tr,u);
}
}
LL query(Tree tr[],int u,int l,int r){
if(tr[u].l >= l && tr[u].r <= r) return tr[u].val;
LL res = 0;
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) res += query(tr,u << 1,l,r);
if(r > mid) res += query(tr,u << 1 | 1,l,r);
pushup(tr,u);
return res;
}
int main(){
cin >> n;
for(int i = 0;i < n;i ++){
scanf("%d",&a[i]);
w.push_back(a[i]);
}
sort(w.begin(),w.end());
w.erase(unique(w.begin(),w.end()),w.end());
build(tr1,1,1,n);
build(tr2,1,1,n);
LL ans = 0;
for(int i = 0;i < n;i ++){
int t = find(a[i]);
modify(tr1,1,t);
if(t != 1) s[i] += query(tr1,1,1,t - 1);
}
for(int i = n - 1;i >= 0;i --){
int t = find(a[i]);
modify(tr2,1,t);
if(t != n) ls[i] += query(tr2,1,t + 1,n);
}
for(int i = 1;i < n - 1;i ++)
ans += s[i] * ls[i];
cout << ans;
return 0;
}
进阶:洛谷:冒泡排序
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 200010;
typedef long long LL;
int n,m;
LL a[N],p[N],c[N],w[N];
struct Tree{
int l,r;
LL cnt,sum;
}tr[N * 4];
void pushup(int u){
tr[u].cnt = tr[u << 1].cnt + tr[u << 1 | 1].cnt;
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
void build(int u,int l,int r){
if(l == r)
tr[u] = {l,r,w[l],w[l]* l};
else{
int mid = l + r >> 1;
tr[u] = {l,r};
build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
pushup(u);
}
}
void modify(int u,int x,int v){
if(tr[u].l == tr[u].r && tr[u].l == x){
tr[u].cnt += v;
tr[u].sum = tr[u].cnt * tr[u].l;
}
else{
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) modify(u << 1,x,v);
else modify(u << 1 | 1,x,v);
pushup(u);
}
}
LL cntquery(int u,int l,int r){
if(tr[u].l >= l && tr[u].r <= r) return tr[u].cnt;
LL res = 0;
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) res = cntquery(u << 1,l,r);
if(r > mid) res += cntquery(u << 1 | 1,l,r);
pushup(u);
return res;
}
LL sumquery(int u,int l,int r){
if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
LL res = 0;
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) res = sumquery(u << 1,l,r);
if(r > mid) res += sumquery(u << 1 | 1,l,r);
pushup(u);
return res;
}
int main(){
cin >> n >> m;
build(1,1,n);
for(int i = 1;i <= n;i ++){
cin >> a[i];
c[i] = cntquery(1,a[i],n);
}
for(int i = 1;i <= n;i ++) w[c[i]] ++;
build(1,0,n - 1);
while(m --){
int op,x;
cin >> op >> x;
if(op == 2){
if(x >= n - 1) cout << 0 << endl;
else cout << sumquery(1,x + 1,n - 1) - cntquery(1,x + 1,n - 1) * x << endl;
}
else{
modify(1,c[x],-1),modify(1,c[x + 1],-1);
if(a[x] > a[x + 1]) c[x + 1] --;
else c[x] ++ ;
swap(c[x],c[x + 1]);
swap(a[x],a[x + 1]);
modify(1,c[x],1),modify(1,c[x + 1],1);
}
}
return 0;
}
4、线段树&&位运算(拆位线段树)
一个线段树不可惧,但遇到位运算可就头疼了,比如下面这题:
洛谷:XOR on Segment
由操作一可知我们要维护区间和,但很明显,区间和与位运算操作不共融,所以我们需要将这些数拆成二进制位存储到线段树中进行操作,具体操作可以看洛谷题解。
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
typedef long long LL;
const int N = 100010;
int n,m;
LL a[N],w[N];
struct Tree{
int l,r;
LL cnt,xors;
}tr[20][4 * N];
void pushup(int u,int i){
tr[i][u].cnt = tr[i][u << 1].cnt + tr[i][u << 1 | 1].cnt;
}
void pushdown(int u,int i){
Tree &root = tr[i][u],&left = tr[i][u << 1],&right = tr[i][u << 1 | 1];
if(root.xors){
left.xors ^= root.xors, left.cnt = (left.r - left.l + 1) - left.cnt;
right.xors ^= right.xors,right.cnt = (right.r - right.l + 1) - right.cnt;
root.xors = 0;
}
}
void build(int i,int u,int l,int r){
if(l == r) tr[i][u] = {l,r,(a[l] & w[i]) != 0,0};
else{
tr[i][u] = {l,r};
int mid = l + r >> 1;
build(i,u << 1,l,mid),build(i,u << 1 | 1,mid + 1,r);
pushup(u,i);
}
}
void modify(int i,int u,int l,int r){
if(tr[i][u].l >= l && tr[i][u].r <= r){
tr[i][u].xors ^= 1;
tr[i][u].cnt = (tr[i][u].r - tr[i][u].l + 1) - tr[i][u].cnt;
}
else{
pushdown(u,i);
int mid = tr[i][u].l + tr[i][u].r >> 1;
if(l <= mid) modify(i,u << 1,l,r);
if(r > mid) modify(i,u << 1 | 1,l,r);
pushup(u,i);
}
}
LL query(int i,int u,int l,int r){
if(tr[i][u].l >= l && tr[i][u].r <= r){
return tr[i][u].cnt;
}
pushdown(u,i);
int mid = tr[i][u].l + tr[i][u].r >> 1;
LL res = 0;
if(l <= mid) res = query(i,u << 1,l,r);
if(r > mid) res += query(i,u << 1 | 1,l,r);
pushup(u,i);
return res;
}
int main(){
cin >> n;
for(int i = 0;i < 20;i ++) w[i] = 1 << i;
for(int i = 1;i <= n;i ++) scanf("%lld",&a[i]);
for(int i = 0;i < 20;i ++) build(i,1,1,n);
cin >> m;
while(m --){
int op,l,r;
scanf("%d%d%d",&op,&l,&r);
if(op == 1){
LL ans = 0;
for(int i = 0;i < 20;i ++){
ans += query(i,1,l,r) * w[i];
}
printf("%lld\n",ans);
}
else{
int x;
scanf("%d",&x);
for(int i = 0;i < 20;i ++)
if(x & w[i]) modify(i,1,l,r);
}
}
return 0;
}
5、较难pushup的线段树
题目:洛谷:STEP
该题中线段树父节点维护的值更新时不能只是对左右儿子节点的值简单的加减或者取最大值,还需要考虑左右儿子之间连接的部分。只要把pushup操作搞定,这道题的难点就被攻破了。
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N = 200010;
int n,m;
struct Tree{
int l,r;
int pre,last,all,le,ri;
}tr[4 * N];
void pushup(int u){
Tree &root = tr[u],&left = tr[u << 1],&right = tr[u << 1 | 1];
root.le = left.le,root.ri = right.ri;
if(left.ri == right.le){
root.pre = left.pre,root.last = right.last;
root.all = max(left.all,right.all);
}
else{
root.all = max(left.all,right.all);
root.all = max(root.all,left.last + right.pre);
if(left.all == left.r - left.l + 1)
root.pre = left.all + right.pre;
else
root.pre = left.pre;
if(right.all == right.r - right.l + 1)
root.last = right.all + left.last;
else
root.last = right.last;
}
}
void build(int u,int l,int r){
if(l == r) tr[u] = {l,r,1,1,1,0,0};
else{
tr[u] = {l,r};
int mid = l + r >> 1;
build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
pushup(u);
}
}
void modify(int u,int x){
if(tr[u].l == tr[u].r && tr[u].l == x) tr[u].le ^= 1,tr[u].ri ^= 1;
else{
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) modify(u << 1,x);
else modify(u << 1 | 1,x);
pushup(u);
}
}
int main(){
cin >> n >> m;
build(1,1,n);
while(m --){
int x;
scanf("%d",&x);
modify(1,x);
cout << tr[1].all << endl;
}
return 0;
}
类似的:
洛谷:Hotel G
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N = 50010;
int n,m;
struct Tree{
int l,r;
int lmax,rmax,sum,lazy;
}tr[4 * N];
void pushup(int u){
tr[u].sum = max(tr[u << 1].sum,tr[u << 1 | 1].sum);
tr[u].sum = max(tr[u].sum,tr[u << 1].rmax + tr[u << 1 | 1].lmax);
if(tr[u << 1].sum == tr[u << 1].r - tr[u << 1].l + 1)
tr[u].lmax = tr[u << 1].sum + tr[u << 1 | 1].lmax;
else
tr[u].lmax = tr[u << 1].lmax;
if(tr[u << 1 | 1].sum == tr[u << 1 | 1].r - tr[u << 1 | 1].l + 1)
tr[u].rmax = tr[u << 1].rmax + tr[u << 1 | 1].sum;
else
tr[u].rmax = tr[u << 1 | 1].rmax;
}
void pushdown(int u){
Tree &root = tr[u],&left = tr[u << 1],&right = tr[u << 1 | 1];
if(root.lazy){
left.lazy = root.lazy,right.lazy = root.lazy;
if(root.lazy == 1){
left.lmax = left.rmax = left.sum = 0;
right.lmax = right.rmax = right.sum = 0;
}
else if(root.lazy == 2){
left.lmax = left.sum = left.rmax = left.r - left.l + 1;
right.lmax = right.sum = right.rmax = right.r - right.l + 1;
}
root.lazy = 0;
}
}
void build(int u,int l,int r){
if(l == r) tr[u] = {l,r,1,1,1,0};
else{
tr[u] = {l,r};
int mid = l + r >> 1;
build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
pushup(u);
}
}
void modify(int u,int l,int r,int x){
if(tr[u].l >= l && tr[u].r <= r){
tr[u].lazy = x;
if(x == 1){
tr[u].lmax = tr[u].rmax = tr[u].sum = 0;
}
else if(x == 2){
tr[u].lmax = tr[u].rmax = tr[u].sum = tr[u].r - tr[u].l + 1;
}
}
else{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1,l,r,x);
if(r > mid) modify(u << 1 | 1,l,r,x);
pushup(u);
}
}
int query(int u,int len){
if(tr[u].l == tr[u].r) return tr[u].l;
pushdown(u);
if(tr[u << 1].sum >= len) return query(u << 1,len);
if(tr[u << 1].rmax + tr[u << 1 | 1].lmax >= len) return (tr[u].r + tr[u].l >> 1) - tr[u << 1].rmax + 1;
else return query(u << 1 | 1,len);
}
int main(){
cin >> n >> m;
build(1,1,n);
while(m --){
int op;
scanf("%d",&op);
if(op == 1){
int x;
scanf("%d",&x);
if(x > tr[1].sum) cout << 0 << endl;
else{
int left = query(1,x);
printf("%d\n",left);
modify(1,left,left + x - 1,1);
}
}
else{
int x,y;
scanf("%d%d",&x,&y);
modify(1,x,x + y - 1,2);
}
}
return 0;
}
6、与其它知识点串接的线段树
题目:洛谷:New Year Tree
该题用到了树的dfs序及状态压缩,具体见洛谷题解。
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
typedef long long LL;
const int N = 400010;
int in[N],out[N],timestamp,pos[N];
int h[N],e[2 * N],ne[2 * N],idx;
int n,m;
LL color[N];
struct Tree{
int l,r;
LL sum,lazy;
}tr[N * 4];
void add(int a,int b){
e[idx] = b,ne[idx] = h[a],h[a] = idx ++;
}
void pushup(int u){
tr[u].sum = tr[u << 1].sum | tr[u << 1 | 1].sum;
}
void pushdown(int u){
Tree &root = tr[u],&left = tr[u << 1],&right = tr[u << 1 | 1];
if(root.lazy){
left.lazy = root.lazy,left.sum = 1ll << root.lazy;
right.lazy = root.lazy,right.sum = 1ll << root.lazy;
root.lazy = 0;
}
}
void build(int u,int l,int r){
if(l == r) tr[u] = {l,r,1ll << color[pos[l]],0};
else{
tr[u] = {l,r};
int mid = tr[u].l + tr[u].r >> 1;
build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
pushup(u);
}
}
void modify(int u,int l,int r,LL c){
if(tr[u].l >= l && tr[u].r <= r){
tr[u].lazy = c;
tr[u].sum = 1ll << c;
}
else{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1,l,r,c);
if(r > mid) modify(u << 1 | 1,l,r,c);
pushup(u);
}
}
LL query(int u,int l,int r){
if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
LL res = 0;
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) res = query(u << 1,l,r);
if(r > mid) res |= query(u << 1 | 1,l,r);
pushup(u);
return res;
}
LL lowbit(LL x){
return x & (-x);
}
void dfs(int x,int fa){
++ timestamp;
in[x] = timestamp;
pos[timestamp] = x;
for(int i = h[x];i != -1;i = ne[i]){
int j = e[i];
if(j != fa){
dfs(j,x);
}
}
out[x] = timestamp;
}
int main(){
cin >> n >> m;
memset(h,-1,sizeof h);
for(int i = 1;i <= n;i ++) cin >> color[i];
for(int i = 1;i < n;i ++){
int a,b;
scanf("%d%d",&a,&b);
add(a,b),add(b,a);
}
dfs(1,-1);
build(1,1,n);
while(m --){
int op,x;
scanf("%d%d",&op,&x);
if(op == 1){
LL c;
scanf("%lld",&c);
int l = in[x],r = out[x];
modify(1,l,r,c);
}
else{
int l = in[x],r = out[x];
LL ans = 0;
LL t = query(1,l,r);
for(LL i = t;i;i -= lowbit(i)) ans += 1;
cout << ans << endl;
}
}
return 0;
}
7、对区间修改操作进行暴力单点修改的线段树
题目:洛谷:The Child and Sequence
由于取模操作与区间和不能共容,需要对其进行暴力取模,由于当区间最大值小于模数时,该区间取模不会发生改变,故可对此做一个优化,在modify操作中可见。
代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
const int N = 100010;
typedef long long LL;
int n,m;
LL w[N];
struct Tree{
int l,r;
LL sum,maxn;
}tr[4 * N];
void pushup(int u){
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
tr[u].maxn = max(tr[u << 1].maxn,tr[u << 1 | 1].maxn);
}
void build(int u,int l,int r){
if(l == r) tr[u] = {l,r,w[l],w[l]};
else{
tr[u] = {l,r};
int mid = l + r >> 1;
build(u << 1,l,mid),build(u << 1 | 1,mid + 1,r);
pushup(u);
}
}
void mod(int u,int l,int r,int x){
if(tr[u].maxn < x) return;
if(tr[u].l >= l && tr[u].r <= r && tr[u].l == tr[u].r)
tr[u].sum = tr[u].maxn = tr[u].sum % x;
else{
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) mod(u << 1,l,r,x);
if(r > mid) mod(u << 1 | 1,l,r,x);
pushup(u);
}
}
void modify(int u,int x,int t){
if(tr[u].l == tr[u].r && tr[u].l == x)
tr[u].maxn = tr[u].sum = t;
else{
int mid = tr[u].l + tr[u].r >> 1;
if(x <= mid) modify(u << 1,x,t);
else modify(u << 1 | 1,x,t);
pushup(u);
}
}
LL query(int u,int l,int r){
if(tr[u].l >= l && tr[u].r <= r) return tr[u].sum;
LL res = 0;
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) res = query(u << 1,l,r);
if(r > mid) res += query(u << 1 | 1,l,r);
pushup(u);
return res;
}
int main(){
cin >> n >> m;
for(int i = 1;i <= n;i ++) cin >> w[i];
build(1,1,n);
while(m --){
int op;
scanf("%d",&op);
if(op == 1){
int l,r;
scanf("%d%d",&l,&r);
printf("%lld\n",query(1,l,r));
}
else if(op == 2){
int l,r,x;
scanf("%d%d%d",&l,&r,&x);
mod(1,l,r,x);
}
else{
int x,t;
scanf("%d%d",&x,&t);
modify(1,x,t);
}
}
return 0;
}