题目链接:poj3468
题意:给定一段数组,有两种操作,一种是给某段区间加c,另一种是查询一段区间的和
思路:暴力的方法是每次都给这段区间的点加c,查询也遍历一遍区间,复杂度是n*n,肯定过不去,另一种思路是用线段树记录区间的和,每次查询的复杂度是lgn,修改不必更新到每个点,当某个区间全被修改时,我们可以给它加一个懒惰标记,表示这个区间的所有下面节点都需要更新,只是因为现在不需要使用而暂时没有更新。这样修改的复杂度也降到了lgn
ac代码:
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; const int maxn=1e5+10; long long num[maxn],sum[maxn*4],lazy[maxn*4]; void pushdown(int rt,int len1,int len2)//向下更新懒惰标记 { if(lazy[rt]) { lazy[rt*2]+=lazy[rt];//注意是+=而不是= lazy[rt*2+1]+=lazy[rt]; sum[rt*2]+=lazy[rt]*len1; sum[rt*2+1]+=lazy[rt]*len2; sum[rt]=sum[rt*2]+sum[rt*2+1]; lazy[rt]=0; } } void build(int st,int en,int rt) { if(st==en) { sum[rt]=num[st]; return; } build(st,(st+en)/2,rt*2); build((st+en)/2+1,en,rt*2+1); sum[rt]=sum[rt*2]+sum[rt*2+1]; } void add(int l,int r,int c,int st,int en,int rt) { int md=(st+en)/2; if(l<=st&&r>=en) { lazy[rt]+=c; sum[rt]+=(en-st+1)*c; return ; } pushdown(rt,md-st+1,en-md); if(r>=md+1) { add(l,r,c,md+1,en,rt*2+1); } if(l<=md) { add(l,r,c,st,md,rt*2); } sum[rt]=sum[rt*2]+sum[rt*2+1]; } long long quer(int l,int r,int rt,int st,int en) { long long res=0,md=(st+en)/2; if(l<=st&&r>=en) return sum[rt]; pushdown(rt,md-st+1,en-md); if(r>=md+1) res+=quer(l,r,rt*2+1,md+1,en); if(l<=md) res+=quer(l,r,rt*2,st,md); return res; } int main() { char comd; int n,q; cin>>n>>q; for(int i=1;i<=n;i++) scanf("%lld",&num[i]); build(1,n,1); for(int i=1;i<=q;i++) { int l,r,c; cin>>comd; if(comd=='C') { scanf("%d %d %d",&l,&r,&c); add(l,r,c,1,n,1); } else { scanf("%d %d",&l,&r); printf("%lld ",quer(l,r,1,1,n)); } } return 0; }
提升题:hdu6315
题意:有ab两个数组,有两种操作,一种是给a数组的一段区间加一,另一种是求a/b数组的累加和
思路:只有当一段区间最大的a大于最小的b时,这段区间的答案才会发生改变,如果没有发生改变,那么我们就不必要去给子区间修改最大a的值。我们给这段区间加个懒惰标记就可以了,以后浏览到这个区间时我们再修改。复杂度为nlgn
ac代码:
#include <iostream> #include <cstdio> #include <algorithm> using namespace std; const int maxn=1e5+10; int sum[maxn*4],maxa[maxn*4],minb[maxn*4],b[maxn],lazy[maxn*4]; void pushup(int rt) { sum[rt]=sum[rt*2+1]+sum[rt*2]; maxa[rt]=max(maxa[rt*2],maxa[rt*2+1]); minb[rt]=min(minb[rt*2],minb[rt*2+1]); } void pushdowm(int rt) { if(lazy[rt]) { lazy[rt*2]+=lazy[rt]; lazy[rt*2+1]+=lazy[rt]; maxa[rt*2]+=lazy[rt]; maxa[rt*2+1]+=lazy[rt]; lazy[rt]=0; } } void build(int st,int en,int rt) { if(st==en) { minb[rt]=b[st]; return; } build(st,(st+en)/2,rt*2); build((st+en)/2+1,en,rt*2+1); pushup(rt); } void add(int l,int r,int st,int en,int rt) { int md=(st+en)/2; pushdowm(rt); if(l<=st&&r>=en) { maxa[rt]++; if(maxa[rt]>=minb[rt]) { if(st!=en) { add(l,r,md+1,en,rt*2+1); add(l,r,st,md,rt*2); pushup(rt); } else { while(maxa[rt]>=minb[rt]) { minb[rt]+=b[st]; sum[rt]++; } } } else lazy[rt]++; return; } else { if(l<=md) add(l,r,st,md,rt*2); if(r>=md+1) add(l,r,md+1,en,rt*2+1); } pushup(rt); } int quer(int l,int r,int st,int en,int rt) { pushdowm(rt); int md=(st+en)/2; if(l<=st&&r>=en) return sum[rt]; int res=0; if(l<=md) res+=quer(l,r,st,md,rt*2); if(r>=md+1) res+=quer(l,r,md+1,en,rt*2+1); pushup(rt); return res; } int main() { int n,q; char comd[10]; while(cin>>n>>q) { for(int i=0;i<maxn*4;i++)sum[i]=0,maxa[i]=0,minb[i]=0,lazy[i]=0; for(int i=0;i<maxn;i++)lazy[i]=0; for(int i=1; i<=n; i++) scanf("%d",&b[i]); build(1,n,1); for(int i=1; i<=q; i++) { int l,r; scanf("%s %d %d",&comd,&l,&r); if(comd[0]=='a') add(l,r,1,n,1); else printf("%d ",quer(l,r,1,n,1)); } } return 0; }
总结:懒惰标记可以解决一些区域修改问题