You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. One type of op
eration is to add some given number to each number in a given interval. The other is to ask for the
sum of numbers in a given interval.
1.给[a ,b]整体上加上一个常数c。
2.查询[a ,b]区间的和。
Input
The first line contains two numbers N and Q. 1 ≤ N,Q ≤ 100000.
The second line contains N numbers, the initial values of A1, A2, ... , AN. -1000000000 ≤ Ai ≤ 1000000000.
Each of the next Q lines represents an operation.
"C a b c" means adding c to each of Aa, Aa+1, ... , Ab. -10000 ≤ c ≤ 10000.
"Q a b" means querying the sum of Aa, Aa+1, ... , Ab.
Output
You need to answer all Q commands in order. One answer in a line. The sums may exceed the range of 32-bit integers
Sample Input
10 5
1 2 3 4 5 6 7 8 9 10
Q 4 4
Q 1 10
Q 2 4
C 3 6 3
Q 2 4
Sample Output
4
55
9
15
Sol1:Lazy标记
#include<bits/stdc++.h> using namespace std; long long sum[800000],n,q,tt[800000],lazy[800000],size[800000],a1,a2,a3; char s; void a_lazy(int p,int v) //打Lazy标记操作 { sum[p]+=size[p]*v; //更新SUM的值,体现加值的操作效果 lazy[p]+=v; } void push_down(int p) //下放标记,其与上面那个是两个含义 { a_lazy(p*2,lazy[p]); a_lazy(p*2+1,lazy[p]); lazy[p]=0; } void updata(int p) { sum[p]=sum[p*2]+sum[p*2+1]; //用左右子结点的SUM值“更新”父亲点的,注意这里是赋值,而不是累加 //好比我们对[1..10]加了5,于是标记只打在[1..10]上,并不下传 //然后对[1..5]加上6,于是加5的标记要传到两个子结点上,然后再将加6的标记传到[1..5】上 //于是得到[1..5]的权值为5*11=55,[6..10]的权值为5*5=25 //于是[1..10]的权值为55+25=80 size[p]=size[p*2]+size[p*2+1]; } void build(int p,int l,int r) { if(l==r) { sum[p]=tt[l],size[p]=1; return; } int mid=(l+r)/2; build(p*2,l,mid);build(p*2+1,mid+1,r); updata(p); } void q_add(int p,int l,int r,int x,int y,int v) { if(l>=x&&r<=y) //只有当线段树上区间为当前操作区间的子集时,才打上lazy标记 { a_lazy(p,v); return; } //如果没有返回的话,说明线段树上的区间大于当前操作的区间 int mid=(l+r)/2; push_down(p); //此时要将P上的标记,进行下传 if(mid>=x)q_add(p*2,l,mid,x,y,v); if(mid<y)q_add(p*2+1,mid+1,r,x,y,v); updata(p); //信息及时上传 } long long query(int p,int l,int r,int x,int y) { if(l>=x&&r<=y) //sum[p]是真实体现这一段数字和的,标记带来的影响已统计进去了 return sum[p]; long long ans=0; push_down(p); //此时也要进行标记下传,因为要统计的区间可能从前没有访问过,所以标记根本没有下传, 其sum值也不是真实的 int mid=(l+r)/2; if(mid>=x)ans+=query(p*2,l,mid,x,y); if(mid<y)ans+=query(p*2+1,mid+1,r,x,y); return ans; } int main() { scanf("%lld%lld",&n,&q); for(int i=1;i<=n;i++)scanf("%lld",&tt[i]); build(1,1,n); for(int i=1;i<=q;i++) { cin>>s; if(s=='Q') { scanf("%lld%lld",&a1,&a2); printf("%lld ",query(1,1,n,a1,a2)); } else { scanf("%lld%lld%lld",&a1,&a2,&a3); q_add(1,1,n,a1,a2,a3); } } return 0; }
#include<cstdio> #define mid (s[x].l+s[x].r>>1) int n,m,d[100001]; char p[3]; struct oo{int l,r;long long lazy,sum;}s[400001]; void build(int x,int l,int r) { s[x].l=l,s[x].r=r; if(l==r){s[x].sum=d[l];return ;} build(x<<1,l,mid);build(x<<1|1,mid+1,r); s[x].sum=s[x<<1].sum+s[x<<1|1].sum; } void pushdown(int x) //lazy标记下传 { int l=x<<1,r=x<<1|1; s[l].sum+=(s[l].r-s[l].l+1)*s[x].lazy; s[r].sum+=(s[r].r-s[r].l+1)*s[x].lazy; s[l].lazy+=s[x].lazy;s[r].lazy+=s[x].lazy; s[x].lazy=0; } void change(int x,int l,int r,int v) { if(l<=s[x].l&&r>=s[x].r) { s[x].sum+=(s[x].r-s[x].l+1)*v; s[x].lazy+=v; return ; } if(s[x].lazy) //记得标记下传 pushdown(x); if(l<=mid) change(x<<1,l,r,v); if(r>mid) change(x<<1|1,l,r,v); s[x].sum=s[x<<1].sum+s[x<<1|1].sum; } long long get(int x,int l,int r) { if(l<=s[x].l&&r>=s[x].r) return s[x].sum; if(s[x].lazy) //记得标记下传 pushdown(x); long long ans=0; if(l<=mid)ans+=get(x<<1,l,r); if(r>mid)ans+=get(x<<1|1,l,r); s[x].sum=s[x<<1].sum+s[x<<1|1].sum; return ans; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++)scanf("%d",&d[i]); build(1,1,n);int x,y,z; while(m--) { scanf("%s",p+1); if(p[1]=='Q') { scanf("%d%d",&x,&y); printf("%lld ",get(1,x,y)); } else { scanf("%d%d%d",&x,&y,&z); change(1,x,y,z); } } }
所谓标记永久化,即只在线段树上的区间为当前操作区间的子集时,
才打上标记,且标记并不下发到子结点。
//设树的结构为 p1
// /
// p2
// /
// p3
//当我们有个标记打在p2上面时,则从p1开始走,就事先算出对p1的影响
//由于影响已算出来了,即sum[p1]体现的就是这一段真实的数字和
//于是也就用不着updata上传信息了
//然后走到p2,算出对p2的影响,由于并没有接着向下走
//所以事实上是对p3区间是有影响的,但标记没有下传
//于是在算p3的时候,需要累加从p1到p2这一段的标记量的累加
下面这个代码是Pku3468 A Simple Problem with Integers,代码来自zy
#include <cstdio> #include <algorithm> using namespace std; typedef long long ll; const int maxn=1e5+5; int n,m; ll a[maxn]; ll sum[maxn<<2],add[maxn<<2]; void updata(int p) { sum[p]=sum[p<<1]+sum[p<<1|1]; } void build(int p,int l,int r) { if(l==r) { sum[p]=a[l]; return; } int mid=(l+r)>>1; build(p<<1,l,mid); build(p<<1|1,mid+1,r); updata(p); } void change(int p,int l,int r,int L,int R,ll v) { sum[p]+=v*(min(R,r)-max(L,l)+1); //记录下对区间P的实际贡献 if(L<=l&&r<=R) { add[p]+=v; return; } int mid=(l+r)>>1; if(L<=mid) //如果操作的左边界小于等于mid时,落在左子树 change(p<<1,l,mid,L,R,v); if(R>mid) //如果操作的右边界大于mid,落在右子树 change(p<<1|1,mid+1,r,L,R,v); } ll query(int p,int l,int r,int L,int R) { if(L<=l&&r<=R) return sum[p]; int mid=(l+r)>>1; ll res=add[p]*(min(R,r)-max(L,l)+1); //累加一路走来,遇到的标记,注意res是个局部变量 if(L<=mid) res+=query(p<<1,l,mid,L,R); if(R>mid) res+=query(p<<1|1,mid+1,r,L,R); return res; } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%lld",a+i); build(1,1,n); for(int i=1;i<=m;i++) { int opt,l,r;ll v; char p[3]; scanf("%s",p+1); int x,y;scanf("%d%d",&l,&r); if(p[1]=='C') { scanf("%lld",&v); change(1,1,n,l,r,v); } else printf("%lld ",query(1,1,n,l,r)); } return 0; }
这个是xyh的
#include <cstdio> #include <algorithm> using namespace std; typedef long long ll; const int maxn=1e5+5; int n,m; ll a[maxn]; struct segment_tree { ll sum[maxn<<2],add[maxn<<2]; void updata(int p) { sum[p]=sum[p<<1]+sum[p<<1|1]; } void build(int p,int l,int r) { if(l==r) {sum[p]=a[l];return;} int mid=(l+r)>>1; build(p<<1,l,mid); build(p<<1|1,mid+1,r); updata(p); } void change(int p,int l,int r,int L,int R,ll v) { sum[p]+=v*(R-L+1); if(L<=l&&r<=R) { add[p]+=v; return; } int mid=(l+r)>>1; if(R<=mid) change(p<<1,l,mid,L,R,v); else if(L>mid) change(p<<1|1,mid+1,r,L,R,v); else change(p<<1,l,mid,L,mid,v),change(p<<1|1,mid+1,r,mid+1,R,v); } ll query(int p,int l,int r,int L,int R) { if(L<=l&&r<=R) return sum[p]; int mid=(l+r)>>1; ll res=add[p]*(R-L+1); //累加一路走来,遇到的标记 if(R<=mid) res+=query(p<<1,l,mid,L,R); else if(L>mid) res+=query(p<<1|1,mid+1,r,L,R); else res+=query(p<<1,l,mid,L,mid)+query(p<<1|1,mid+1,r,mid+1,R); return res; } }T; int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) scanf("%lld",a+i); T.build(1,1,n); for(int i=1;i<=m;i++) { int opt,l,r;ll v; char p[3]; scanf("%s",p+1); int x,y;scanf("%d%d",&l,&r); if(p[1]=='C') { scanf("%lld",&v); T.change(1,1,n,l,r,v); } else printf("%lld ",T.query(1,1,n,l,r)); } return 0; }
下面这个是ljq的
//s[p]代表p这一段的数字和,但凡有标记打在p及p的子结点上,都会体现在s[p]上
//但是在p之上结点打过的标记,则不会体现出来。于是要累加一路走下来的标记
//设树的结构为 p1
// /
// p2
// /
// p3
// /
// p4
//设目标有标记打在p2上,则这个影响量开始只算出在p2上面的,
//即s[p2]的值是真实这一段的数字之和
//然后利用updata上传到p1上
//同理,如果有标记打在p3上,也是开始只算出p3的,然后上传到p2,p1上
//由于修改p2,p3,对p4也是有影响的,但标记并没有下传
//于是在算p4的时候,要累加从p1到p3这一段的标记量之和
//标记不下传,只当线段树上区间是目前操作区间的子集时,才打上标记 //这样对于父区间来说,如果标记只打在子区间上,sum的集合是不完整的,于是要updata //在统计信息的时候,sum代表的时,所有标记打在这个区间上的 //于是因为标记没下传,于是统计要要加上一路走过来时所要累加的量 //例如对[1..10]打上10,[1..5]打上8 //则统计[1..10]的总和时,直接返回sum[1](加上8的那部分在updata时传上来了) //统计[1..5]时,要返回sum[2]+一路走过的标记*5 #include<cstdio> #include<iostream> typedef long long ll; using namespace std; ll a[100001]; struct node { #define ls (p<<1) #define rs (p<<1|1) ll s[400001],cnt[400001]; void updata(int p,int l,int r) { s[p]=s[ls]+s[rs]+cnt[p]*(r-l+1); } void build(int p,int l,int r) { cnt[p]=0; if(l==r) { s[p]=a[l];return ; } int mid=(l+r)>>1; build(ls,l,mid); build(rs,mid+1,r); updata(p,l,r); } void change(int p,int l,int r,int x,int y,int v) //l,r是线段树上的区间 //x,y是当前要操作的 { if(x<=l&&r<=y) //只有当目前线段树上的区间是要操作的区间的子集时 { cnt[p]+=v; //加上标记 s[p]+=1LL*(r-l+1)*v; return ; } int mid=(l+r)>>1; if(x<=mid)change(ls,l,mid,x,y,v); if(y>mid)change(rs,mid+1,r,x,y,v); updata(p,l,r); } long long ans; void query(int p,int l,int r,int x,int y,int cc) { // printf("%d %d %d %d %d %lld %lld ",p,l,r,x,y,s[p],cnt[p]); if(x<=l&&r<=y) { ans+=s[p]; ans+=1LL*cc*(r-l+1); return; } int mid=(l+r)>>1; if(x<=mid)query(ls,l,mid,x,y,cc+cnt[p]); if(y>mid)query(rs,mid+1,r,x,y,cc+cnt[p]); } }tree; int main() { int n,q,x,y,z; char c; scanf("%d%d",&n,&q); for(int i=1;i<=n;i++) scanf("%lld",&a[i]); tree.build(1,1,n); for(int i=1;i<=q;i++) { cin>>c; scanf("%d%d",&x,&y); if(c=='Q') { tree.ans=0; tree.query(1,1,n,x,y,0); printf("%lld ",tree.ans); } else { scanf("%d",&z); tree.change(1,1,n,x,y,z);} } }
#include<iostream> #include<cstdio> #include<cstring> #define pos(i,a,b) for(int i=(a);i<=(b);i++) #define N 201000 using namespace std; int n,m;char p[3]; long long sum[N*4],add[N*4]; int a[N]; void build(int l,int r,int rt) { if(l==r) { sum[rt]=a[l];return; } int mid=(l+r)>>1; build(l,mid,rt<<1); build(mid+1,r,rt<<1|1); sum[rt]=sum[rt<<1]+sum[rt<<1|1]; } void update(int rt,int l,int r,int v,int xl,int xr) { sum[rt]+=v*(min(xr,r)-max(xl,l)+1); if(l>=xl&&r<=xr) //等到区间完全重合时,再给add加上v增加的标记 { add[rt]+=v; return; } int mid=(l+r)>>1; if(xr<=mid) update(rt<<1,l,mid,v,xl,xr); else { if(xl>mid) update(rt<<1|1,mid+1,r,v,xl,xr); else update(rt<<1,l,mid,v,xl,xr),update(rt<<1|1,mid+1,r,v,xl,xr); } } long long query(int rt,int ad,int l,int r,int xl,int xr) { if(xl<=l&&xr>=r) { return sum[rt]+ad*(min(xr,r)-max(xl,l)+1); } int mid=(l+r)>>1; if(xr<=mid) return query(rt<<1,ad+add[rt],l,mid,xl,xr); else { if(xl>mid) return query(rt<<1|1,ad+add[rt],mid+1,r,xl,xr); else return query(rt<<1,ad+add[rt],l,mid,xl,xr)+query(rt<<1|1,ad+add[rt],mid+1,r,xl,xr); } } int main(){ scanf("%d%d",&n,&m); pos(i,1,n) scanf("%d",&a[i]); build(1,n,1); pos(i,1,m) { scanf("%s",p+1); int x,y;scanf("%d%d",&x,&y); if(p[1]=='C') { int k;scanf("%d",&k); update(1,1,n,k,x,y); } else printf("%lld ",query(1,0,1,n,x,y)); } return 0; }
使用动态开点解决上述问题
题目描述
如题,已知一个数列,你需要进行下面两种操作:
1.将某区间每一个数加上x
2.求出某区间每一个数的和
输入输出格式
输入格式:
第一行包含两个整数N、M,分别表示该数列数字的个数和操作的总个数。
第二行包含N个用空格分隔的整数,其中第i个数字表示数列第i项的初始值。
接下来M行每行包含3或4个整数,表示一个操作,具体如下:
操作1: 格式:1 x y k 含义:将区间[x,y]内每个数加上k
操作2: 格式:2 x y 含义:输出区间[x,y]内每个数的和
输出格式:
输出包含若干行整数,即为所有操作2的结果。
输入输出样例
输入样例#1:
5 5
1 5 4 2 3
2 2 4
1 2 3 2
2 3 4
1 1 5 1
2 1 4
输出样例#1:
11
8
20
说明
时空限制:1000ms,128M
数据规模:
对于30%的数据:N<=8,M<=10
对于70%的数据:N<=1000,M<=10000
对于100%的数据:N<=100000,M<=100000
(数据已经过加强_,保证在int64/long long数据范围内)
#include<cstdio> #include<iostream> using namespace std; const int N=300010; struct node{ int ls,rs,lazy; long long sum; }tr[N]; int root=0,cnt=0; void insert(int &root,int l,int r,int ll,int rr,int x) { if(!root) //动态开点 root=++cnt; int b=min(r,rr)-max(l,ll)+1; tr[root].sum+=b*x; if(l>=ll&&r<=rr) { tr[root].lazy+=x; return; } int mid=l+r>>1; if(ll<=mid) insert(tr[root].ls,l,mid,ll,rr,x); if(rr>mid) insert(tr[root].rs,mid+1,r,ll,rr,x); } long long query(int root,int l,int r,int ll,int rr) { if(l>=ll&&r<=rr) return tr[root].sum; int mid=l+r>>1; if(tr[root].lazy) //lazy标记下传 { if(!tr[root].ls) tr[root].ls=++cnt; tr[tr[root].ls].lazy+=tr[root].lazy; tr[tr[root].ls].sum+=tr[root].lazy*(mid-l+1); if(!tr[root].rs) tr[root].rs=++cnt; tr[tr[root].rs].lazy+=tr[root].lazy; tr[tr[root].rs].sum+=tr[root].lazy*(r-mid); tr[root].lazy=0; } long long ans=0; if(ll<=mid) ans+=query(tr[root].ls,l,mid,ll,rr); if(rr>mid) ans+=query(tr[root].rs,mid+1,r,ll,rr); return ans; } int main() { int n,m; cin>>n>>m; for(int i=1;i<=n;i++) { int temp; cin>>temp; insert(root,1,n,i,i,temp); } for(int i=1;i<=m;i++) { int ta,tb,tc,td; cin>>ta>>tb>>tc; if(ta==1) { cin>>td; insert(root,1,n,tb,tc,td); } else if(ta==2) { cout<<query(root,1,n,tb,tc)<<endl; } } return 0; }