Poj 3468 A Simple Problem with Integers 相关的8种解法
来源:互联网 发布:数控冲床编程难不难 编辑:程序博客网 时间:2024/06/10 21:16
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 longint 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 longint 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 longint 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 longLL 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 longLL 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;}
6.B[i]和C[i]都求前缀和:
#include <iostream>#include <stdio.h>#include <stdlib.h>using namespace std;#define N 100009#define LL long longLL 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;charaction; #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endifscanf("%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;}
8.与7解法相似:
事实上,还可以不通过求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];intA[N];intn, 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;charaction; #ifndef ONLINE_JUDGE freopen("in.txt","r",stdin); #endifscanf("%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
- Poj 3468 A Simple Problem with Integers 相关的8种解法
- POJ 3468 A Simple Problem with Integers 树状数组解法
- POJ 3468 A Simple Problem With Integers
- poj 3468 A Simple Problem with Integers
- poj 3468 A Simple Problem with Integers
- POJ 3468 A Simple Problem with Integers
- poj 3468 A Simple Problem with Integers
- poj 3468 A Simple Problem with Integers
- poj 3468 A Simple Problem with Integers
- poj 3468 A Simple Problem with Integers
- POJ-3468-A Simple Problem with Integers
- POJ 3468 A Simple Problem with Integers
- poj 3468 A Simple Problem with Integers
- POJ 3468 - A Simple Problem with Integers
- poj 3468 A Simple Problem with Integers
- poj 3468 A Simple Problem with Integers
- POJ 3468 A Simple Problem with Integers
- poj 3468 A Simple Problem with Integers
- Memcached初探
- ebs workflow历史记录清楚程序
- 3D图形矩阵变换总结
- 黑马程序员三、多线程
- jquery中val,text,html,attr的区别
- Poj 3468 A Simple Problem with Integers 相关的8种解法
- 思考:矩阵及变换,以及矩阵在DirectX和OpenGL中的运用
- Boost 入门及其VS2005下编译boost库
- shell ip扫描
- X公司的流程改造之路 (二) [课程报告]
- 我自己写的一个表情组件
- GNU ARM汇编--(十三)GNU ARM汇编下的linker script
- 【Q&A】stl容器去除重复的元素
- wishbone