树状数组-线段树
2016-03-15 13:01
316 查看
POJ 3468
操作:区间价值,区间求和
poj 3468 树状数组解法
一 算法
树状数组天生用来动态维护数组前缀和,其特点是每次更新一个元素的值,查询只能查数组的前缀和,
但这个题目求的是某一区间的数组和,而且要支持批量更新某一区间内元素的值,怎么办呢?实际上,
还是可以把问题转化为求数组的前缀和。
A[i]…A
的共同增量,n是数组的大小。那么update操作可以转化为:
1)令delta[s] = delta[s] + d,表示将A[s]…A
同时增加d,但这样A[t+1]…A
就多加了d,所以
2)再令delta[t+1] = delta[t+1] - d,表示将A[t+1]…A
同时减d
那么前缀和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的前缀和是不断变化的,可以用两个树状数组来维护。
排在22名,但很奇怪,如果用long long变量,用gcc提交的话就要慢很多。
二 代码
C代码 收藏代码
事实上,还可以不通过求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的区间和,而线段树可以直接求出区间和,所以又得到了另外一种
解法:
C代码 收藏代码
但不能直接修改某个区间的值,必须引入一个额外的数组,如这题的delta数组,把对区间的修改转化为对两个端点的修改。
线段树:
操作:区间价值,区间求和
poj 3468 树状数组解法
一 算法
树状数组天生用来动态维护数组前缀和,其特点是每次更新一个元素的值,查询只能查数组的前缀和,
但这个题目求的是某一区间的数组和,而且要支持批量更新某一区间内元素的值,怎么办呢?实际上,
还是可以把问题转化为求数组的前缀和。
首先,看更新操作update(s, t, d)把区间A[s]...A[t]都增加d,我们引入一个数组delta[i],表示
A[i]…A
的共同增量,n是数组的大小。那么update操作可以转化为:
1)令delta[s] = delta[s] + d,表示将A[s]…A
同时增加d,但这样A[t+1]…A
就多加了d,所以
2)再令delta[t+1] = delta[t+1] - d,表示将A[t+1]…A
同时减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的前缀和是不断变化的,可以用两个树状数组来维护。
树状数组的解法比朴素线段树快很多,如果把long long变量改成__int64,然后用C提交的话,可以达到1047ms,
排在22名,但很奇怪,如果用long long变量,用gcc提交的话就要慢很多。
二 代码
C代码 收藏代码
#include <stdio.h> #define DEBUG #ifdef DEBUG #define debug(...) printf( __VA_ARGS__) #else #define debug(...) #endif #define N 100002 #define lowbit(i) ( i & (-i) ) /* 设delta[i]表示[i,n]的公共增量 */ long long c1 ; /* 维护delta[i]的前缀和 */ long long c2 ; /* 维护delta[i]*i的前缀和 */ long long sum ; int A ; 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; 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的区间和,而线段树可以直接求出区间和,所以又得到了另外一种
解法:
C代码 收藏代码
#include <stdio.h> //#define DEBUG #ifdef DEBUG #define debug(...) printf( __VA_ARGS__) #else #define debug(...) #endif #define N 100002 /* 设delta[i]表示[i,n]的公共增量 */ long long tree1[262144]; /* 维护delta[i]的前缀和 */ long long tree2[262144]; /* 维护delta[i]*i的前缀和 */ long long sum ; int A ; 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; 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; }
两种解法本质上是一样的,其实zkw式线段树 == 树状数组,它们都可以支持查询某个区间的和,以及修改某个点的值,
但不能直接修改某个区间的值,必须引入一个额外的数组,如这题的delta数组,把对区间的修改转化为对两个端点的修改。
线段树:
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<string> #include<queue> #include<algorithm> #include<map> #include<iomanip> #define INF 99999999 using namespace std; const int MAX=100000+10; __int64 sum[MAX<<2],mark[MAX<<2];//sum表示区间和,mark表示父节点更新了但是孩子未更新 void BuildTree(int n,__int64 left,__int64 right){ mark =0; if(left == right){scanf("%I64d",&sum );return;} __int64 mid=left+right>>1; BuildTree(n<<1,left,mid); BuildTree(n<<1|1,mid+1,right); sum =sum[n<<1]+sum[n<<1|1]; } void Upchild(int n,__int64 len){ if(mark ){//表示该区间更新了但是孩子未更新 mark[n<<1]+=mark ;//表示孩子更新了但是孩子的孩子未更新 mark[n<<1|1]+=mark ; sum[n<<1]+=(len-(len>>1))*mark ; sum[n<<1|1]+=(len>>1)*mark ; mark =0;//表示不存在该区间更新了但是孩子未更新的情况 } } void Update(__int64 L,__int64 R,__int64 date,int n,__int64 left,__int64 right){ if(L<=left && right<=R){ sum +=(right-left+1)*date; mark +=date;//表示父节点更新了但是孩子未更新 return; } Upchild(n,right-left+1);//在本次更新前先更新上一次父节点更新但是孩子未更新的孩子 __int64 mid=left+right>>1; if(L<=mid)Update(L,R,date,n<<1,left,mid); if(R>mid)Update(L,R,date,n<<1|1,mid+1,right); sum =sum[n<<1]+sum[n<<1|1]; } __int64 Query(__int64 L,__int64 R,int n,__int64 left,__int64 right){ if(L<=left && right<=R)return sum ; Upchild(n,right-left+1); __int64 mid=left+right>>1,ans=0; if(L<=mid)ans+=Query(L,R,n<<1,left,mid); if(R>mid)ans+=Query(L,R,n<<1|1,mid+1,right); return ans; } int main(){ int m; __int64 a,b,c,n; char s[2]; while(scanf("%I64d%d",&n,&m)!=EOF){ BuildTree(1,1,n); while(m--){ scanf("%s",s); if(s[0] == 'C'){ scanf("%I64d%I64d%I64d",&a,&b,&c); Update(a,b,c,1,1,n); } else{ scanf("%I64d%I64d",&a,&b); printf("%I64d\n",Query(a,b,1,1,n)); } } } return 0; }
相关文章推荐
- .net创建和调用WebService
- Android与HTML+JS交互入门
- 2-SAT
- vc6转2010
- 数组初始化的三种方式
- 怎样用指针指向二维数组
- 字符串暴力
- python之pandas使用:数据的选择
- Python 第九篇:队列Queue、生产者消费者模型、(IO/异步IP/Select/Poll/Epool)、Mysql操作
- 位运算(含应用)
- 培训日报3.14(mysql,guava,穿山甲等)
- 枚举
- (转)java classload 机制 详解
- JQuery中ajax的相关方法总结
- 位运算: 如何用移位运算实现乘法操作
- 智能社的邀请码
- 51NOD——1117 聪明的木匠(可用优先队列解决的贪心算法)
- 8.GitHub实战系列~8.使用GitHub建立自己的免费博客
- 不小心删除了linux mint的通知区域怎么办?(上网icon)
- POST提交数据方式