1.递归线段树,完成时标记:
#include <stdio.h> #include <stdlib.h> using namespace std; #define N 100009 #define lx (x<<1) #define rx (x<<1 | 1) #define MID ((l+r)>>1) #define LL long long int n,q; char c; int a,b,d; int A[N]; LL S[N<<2]; LL D[N<<2]; void pushUp(int x) { S[x] = S[lx] + S[rx]; } void pushDown(int l,int r,int x) { if(D[x]) { D[lx]+=D[x]; D[rx]+=D[x]; S[lx]+=(MID-l+1)*D[x]; S[rx]+=(r-MID)*D[x]; D[x] = 0; } } void build(int l,int r,int x) { if(l == r) { S[x] = A[l]; return; } build(l,MID,lx); build(MID+1,r,rx); pushUp(x); } LL query(int L,int R,int l,int r,int x) { LL ans = 0; if(L<=l && r<=R) { return S[x]; } pushDown(l,r,x); if(L<=MID) ans += query(L,R,l,MID,lx); if(R>=MID+1) ans += query(L,R,MID+1,r,rx); return ans; } void update(int L,int R,int d,int l,int r,int x) { if(L<=l && r<=R) { D[x] += d; S[x] += (r - l + 1)*d; return; } pushDown(l,r,x); if(L<=MID) update(L,R,d,l,MID,lx); if(MID+1<=R) update(L,R,d,MID+1,r,rx); pushUp(x); } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif scanf("%d%d",&n,&q); for(int i=1;i<=n;i++) { scanf("%d",&A[i]); } build(1,n,1); for(int i=0;i<q;i++) { scanf(" %c",&c); if(c == 'Q') { scanf("%d%d",&a,&b); printf("%lld\n",query(a,b,1,n,1)); } else { scanf("%d%d",&a,&b); scanf("%d",&d); update(a,b,d,1,n,1); } } return 0; }
2.递归线段树,未完成时标记:
#include <stdio.h> #include <stdlib.h> using namespace std; #define N 100009 #define lx (x<<1) #define rx (x<<1 | 1) #define MID ((l+r)>>1) #define LL long long int n,q; char c; int a,b,d; int A[N]; LL S[N<<2]; LL D[N<<2]; void build(int l,int r,int x) { if(l == r) { S[x] = A[l]; return; } build(l,MID,lx); build(MID+1,r,rx); S[x] = S[lx] + S[rx]; } LL getSum(int l,int r,int x) { if(a<=l && r<=b) { return S[x] + D[x]*(r-l+1); } if(D[x]) { D[lx]+=D[x]; D[rx]+=D[x]; S[x]+=D[x]*(r-l+1); D[x] = 0; } return (a<=MID ? getSum(l,MID,lx) : 0) + ((MID+1)<=b ? getSum(MID+1,r,rx) : 0); } void update(int l,int r,int x) { if(a<=l && r<=b) { D[x]+=d; return ; } //这一次释放不要忘记 if(D[x]) { D[lx]+=D[x]; D[rx]+=D[x]; D[x] = 0; } if(a<=MID) { update(l,MID,lx); } if(MID+1<=b) { update(MID+1,r,rx); } S[x] = S[lx] + S[rx] + D[lx]*(MID-l+1) + D[rx]*(r-MID); } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif scanf("%d%d",&n,&q); for(int i=1;i<=n;i++) { scanf("%d",&A[i]); } build(1,n,1); for(int i=0;i<q;i++) { scanf(" %c",&c); if(c == 'Q') { scanf("%d%d",&a,&b); printf("%lld\n",getSum(1,n,1)); } else { scanf("%d%d",&a,&b); scanf("%d",&d); update(1,n,1); } } return 0; }
3.非递归线段树(批量更新,区间求和):
#include <iostream> #include <stdio.h> #include <stdlib.h> using namespace std; #define N 100009 #define LL long long int M;//能构造满二叉树的叶子节点数 int A[N]; LL S[1<<18];//能承载N的满二叉树的节点数 LL D[1<<18]; int n,q,a,b,d; char c; void build() { for(int i=2*M-1;i>0;i--) { if(i>=M) { S[i] = A[i-M]; } else { S[i] = S[i<<1] + S[(i<<1)| 1]; } } } LL getSum() { LL sum = 0; int num_a, num_b; int step = 0; num_a = num_b = 0; for(a=a+M-1,b=b+M+1;a^b^1;a>>=1,b>>=1) { //如果是左子树的左子树 if(~a&1) { sum+=S[a^1]; num_a+=(1<<step); } //如果是右子树的右子树 if(b&1) { sum+=S[b^1]; num_b+=(1<<step); } sum+=num_a * D[a>>1]; sum+=num_b * D[b>>1]; step++; } for(a>>=1;a>0;a>>=1) { sum+=(num_a + num_b)*D[a]; } return sum; } void update() { int num_a,num_b; int step = 0; num_a = num_b = 0; for(a=a+M-1,b=b+M+1;a^b^1;a>>=1,b>>=1) { //如果a是偶数 if(~a&1) { S[a^1] +=(1<<step) * d; D[a^1] +=d; num_a+=(1<<step); } //如果b是奇数 if(b&1) { S[b^1] +=(1<<step) * d; D[b^1] +=d; num_b+=(1<<step); } S[a>>1]+=num_a * d; S[b>>1]+=num_b * d; step++; } for(a>>=1;a>0;a>>=1) { S[a]+=(num_a + num_b)*d; } } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif scanf("%d%d",&n,&q); for(M=1;M<n+2;M<<=1); for(int i=1;i<=n;i++) { scanf("%d",A+i); } build(); for(int i=0;i<q;i++) { scanf(" %c",&c); if(c == 'Q') { scanf("%d%d",&a,&b); printf("%lld\n",getSum()); } else { scanf("%d%d%d",&a,&b,&d); update(); } } return 0; }
4.ZKW树状数组,推导公式:(推荐)
需要两个辅助数组:B[i]表示A[1..i]到目前为止共被整体加了多少,C[i]表示A[1..i]到目前为止共被整体加了多少的总和(或者说,C[i]=B[i]*i)。
对于ADD(x, c),只要将B[x]加上c,同时C[x]加上c*x即可(根据C[x]和B[x]间的关系可得);
而ADD(x, c)操作是这样影响A[1..i]的和的:若x<i,则会将A[1..i]的和加上x*c,否则(x>=i)会将A[1..i]的和加上i*c。也就是,A[1..i]之和 = B[i..N]之和 * i + C[1..i-1]之和。
#include <iostream> #include <stdio.h> #include <stdlib.h> using namespace std; #define N 100009 #define LL long long LL B[N]; LL C[N]; int n,q,a,b,d; int temp; char c; LL sum_a,sum_b; int lowbit(int x) { return x&(-x); } void updateB(int x,int d) { while(x>0) { B[x] += d; x -=lowbit(x);//x^=lowbit(x) } } void updateC(int x,int d) { int t = x; while(x<=n) { C[x]+=(LL)t*d; x+=lowbit(x); } } LL getSumB(int x) { LL sum = 0; while(x<=n) { sum+=B[x]; x += lowbit(x); } return sum; } LL getSumC(int x) { LL sum = 0; while(x>0) { sum+=C[x]; x -= lowbit(x);//x^=lowbit(x) } return sum; } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif scanf("%d%d",&n,&q); for(int i=1;i<=n;i++) { scanf("%d",&temp); updateB(i,temp); updateC(i,temp); if(i>1) { updateB(i-1,-temp); updateC(i-1,-temp); } } for(int i=0;i<q;i++) { scanf(" %c",&c); if(c == 'Q') { scanf("%d%d",&a,&b); sum_b = 0; sum_a = 0; a -=1; if(b>0) { sum_b = getSumB(b)*b + getSumC(b-1); } if(a>0) { sum_a = getSumB(a)*a + getSumC(a-1); } printf("%lld\n",sum_b-sum_a); } else { scanf("%d%d%d",&a,&b,&d); updateB(b,d); updateC(b,d); if(a>1) { updateB(a-1,-d); updateC(a-1,-d); } } } return 0; }5.和解法4类似,但B[i]和C[i]两个数组都求后缀和:
#include <iostream> #include <stdio.h> #include <stdlib.h> using namespace std; #define N 100009 #define LL long long LL B[N]; LL C[N]; int n,q,a,b,d; int temp; char c; LL sum_a,sum_b; int lowbit(int x) { return x&(-x); } LL getSum(int x){ LL s1 = 0; LL s2 = 0; LL t = n - x; while (x <= n) { s1 += B[x]; s2 += C[x]; x += lowbit(x); } return s1 * t - s2; } void update(int x, int d){ LL t = (LL) (n - x - 1) * d; while (x>0) { B[x] += d; C[x] += t; x -= lowbit(x); } } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif scanf("%d%d",&n,&q); for(int i=1;i<=n;i++) { scanf("%d",&temp); update(i,temp); update(i-1,-temp); } for(int i=0;i<q;i++) { scanf(" %c",&c); if(c == 'Q') { scanf("%d%d",&a,&b); printf("%lld\n",getSum(a)-getSum(b+1)); } else { scanf("%d%d%d",&a,&b,&d); update(b,d); update(a-1,-d); } } return 0; }
#include <iostream> #include <stdio.h> #include <stdlib.h> using namespace std; #define N 100009 #define LL long long LL B[N]; LL C[N]; int n,q,a,b,d; int temp; char c; LL sum_a,sum_b; int lowbit(int x) { return x&(-x); } LL getSum(int x){ LL s1 = 0; LL s2 = 0; LL t = x; while (x) { s1 += B[x]; s2 += C[x]; x -= lowbit(x); } return s1 * t - s2; } void update(int x, int d){ LL t = (LL) (x - 1) * d; while (x <= n) { B[x] += d; C[x] += t; x += lowbit(x); } } int main() { #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif scanf("%d%d",&n,&q); for(int i=1;i<=n;i++) { scanf("%d",&temp); update(i,temp); update(i+1,-temp); } for(int i=0;i<q;i++) { scanf(" %c",&c); if(c == 'Q') { scanf("%d%d",&a,&b); printf("%lld\n",getSum(b)-getSum(a-1)); } else { scanf("%d%d%d",&a,&b,&d); update(a,d); update(b+1,-d); } } return 0; }7.树状数组解法,推导过程:
首先,看更新操作update(s, t, d)把区间A[s]...A[t]都增加d,我们引入一个数组delta[i],表示
A[i]...A[n]的共同增量,n是数组的大小。那么update操作可以转化为:
1)令delta[s] = delta[s] + d,表示将A[s]...A[n]同时增加d,但这样A[t+1]...A[n]就多加了d,所以
2)再令delta[t+1] = delta[t+1] - d,表示将A[t+1]...A[n]同时减d
然后来看查询操作query(s, t),求A[s]...A[t]的区间和,转化为求前缀和,设sum[i] = A[1]+...+A[i],则
A[s]+...+A[t] = sum[t] - sum[s-1],
那么前缀和sum[x]又如何求呢?它由两部分组成,一是数组的原始和,二是该区间内的累计增量和, 把数组A的原始
值保存在数组org中,并且delta[i]对sum[x]的贡献值为delta[i]*(x+1-i),那么
sum[x] = org[1]+...+org[x] + delta[1]*x + delta[2]*(x-1) + delta[3]*(x-2)+...+delta[x]*1
= org[1]+...+org[x] + segma(delta[i]*(x+1-i))
= segma(org[i]) + (x+1)*segma(delta[i]) - segma(delta[i]*i),1 <= i <= x
这其实就是三个数组org[i], delta[i]和delta[i]*i的前缀和,org[i]的前缀和保持不变,事先就可以求出来,delta[i]和
delta[i]*i的前缀和是不断变化的,可以用两个树状数组来维护。
#include <stdio.h> #define N 100002 #define lowbit(i) ( i & (-i) ) /* 设delta[i]表示[i,n]的公共增量 */ long long c1[N]; /* 维护delta[i]的前缀和 */ long long c2[N]; /* 维护delta[i]*i的前缀和 */ long long sum[N]; int A[N]; int n; long long query(long long *array, int i) { long long tmp; tmp = 0; while (i > 0) { tmp += array[i]; i -= lowbit(i); } return tmp; } void update(long long *array, int i, long long d) { while (i <= n) { array[i] += d; i += lowbit(i); } } int main() { int q, i, s, t, d; long long ans; char action; #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif scanf("%d %d", &n, &q); for (i = 1; i <= n; i++) { scanf("%d", A+i); } for (i = 1; i <= n; i++) { sum[i] = sum[i-1] + A[i]; } while (q--) { getchar(); scanf("%c %d %d", &action, &s, &t); if (action == 'Q') { ans = sum[t] - sum[s-1]; ans += (t+1)*query(c1, t) - query(c2, t); ans -= (s*query(c1, s-1) - query(c2, s-1)); printf("%lld\n", ans); } else { scanf("%d", &d); /* 把delta[i](s<=i<=t)加d,策略是 *先把[s,n]内的增量加d,再把[t+1,n]的增量减d */ update(c1, s, d); update(c1, t+1, -d); update(c2, s, d*s); update(c2, t+1, -d*(t+1)); } } return 0; }
事实上,还可以不通过求s和t的前缀和,而是直接求出[s,t]的区间和,这是因为:
sum[t] = segma(org[i]) + (x+1)*segma(delta[i]) - segma(delta[i]*i) 1 <= i <= t
sum[s-1] = segma(org[i]) + s*segma(delta[i]) - segma(delta[i]*i) 1 <= i <= s-1
[s,t]的区间和可以表示为:
sum[t]-sum[s-1] = org[s] + ... + org[t] + (t+1)*(delta[s] + ... + delta[t]) + (t-s+1)*(delta[1] + ... + delta[s-1])
- (delta[s]*s + ... + delta[t]*t)
= segma(org[i]) +(t+1)* segma(delta[i]) - segma(delta[i]*i) , s <= i <= t
+ (t-s+1)*segma(delta[i]), 1 <= i <= s-1
问题转化为求三个数组org, delta[i]和delta[i]*i的区间和,而线段树可以直接求出区间和,所以又得到了另外一种
#include <stdio.h> #define N 100002 /* 设delta[i]表示[i,n]的公共增量 */ long long tree1[262144]; /* 维护delta[i]的前缀和 */ long long tree2[262144]; /* 维护delta[i]*i的前缀和 */ long long sum[N]; int A[N]; int n, M; /* 查询[s,t]的区间和 */ long long query(long long *tree, int s, int t) { long long tmp; tmp = 0; for (s = s+M-1, t = t+M+1; (s^t) != 1; s >>= 1, t >>= 1) { if (~s&1) { tmp += tree[s^1]; } if (t&1) { tmp += tree[t^1]; } } return tmp; } /* 修改元素i的值 */ void update(long long *tree, int i, long long d) { for (i = (i+M); i > 0; i >>= 1) { tree[i] += d; } } int main() { int q, i, s, t, d; long long ans; char action; #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endif scanf("%d %d", &n, &q); for (i = 1; i <= n; i++) { scanf("%d", A+i); } for (i = 1; i <= n; i++) { sum[i] = sum[i-1] + A[i]; } for (M = 1; M < (n+2); M <<= 1); while (q--) { getchar(); scanf("%c %d %d", &action, &s, &t); if (action == 'Q') { ans = sum[t] - sum[s-1]; ans += (t+1)*query(tree1, s, t)+(t-s+1)*query(tree1, 1, s-1); ans -= query(tree2, s, t); printf("%lld\n", ans); } else { scanf("%d", &d); /* 把delta[i](s<=i<=t)加d,策略是 *先把[s,n]内的增量加d,再把[t+1,n]的增量减d */ update(tree1, s, d); update(tree2, s, d*s); if (t < n) { update(tree1, t+1, -d); update(tree2, t+1, -d*(t+1)); } } } return 0; }
http://kenby.iteye.com/blog/962159
http://www.shuizilong.com/house/archives/poj-3468-a-simple-problem-with-integers/
http://www.cnblogs.com/louisnit/archive/2012/2/29.html