poj 3468 很水的线段树lazy操作,为了理解hdu 3954 的 lazy做的。
来源:互联网 发布:fifaonline3国服数据库 编辑:程序博客网 时间:2024/05/17 05:13
操作:
对一个区间的每个数加上一个数。
询问:
一个区间的和为多少。
这让我想起那道操作是对一个区间的每个数根号,求的也是和。lazy得不同而已。做了几题lazy的题,发觉和别的结合起来的时候我又不懂了。于是重新开始做lazy的基础题。现在又对lazy有了更深的了解。
我发觉我每次写的lazy都是自动写成了在本个区间就释放的lazy,但是没有仔细思考过为什么。
如果,在本个区间不释放lazy,那么子区间的孩子的改变没有体现在父亲节点上,也不能用我写的push_up来体现(子区间没有释放,父亲节点已经释放了,不能用没有释放的去改变释放的)。假设区间[1,5],我们去改变[1,3],[1,3]的lazy值变成1,没有释放。但是我们下一次去查询[1,5]的时候,体现不了[1,3]的改变。
加了个油!
ps:当我今天知道树状数组这题的做法的时候,我刹那间觉得原来简单的题也可以摇身一变,变成不简单的题。。。
/*Pro: 0Sol:date:*/#include <iostream>#include <cstdio>#include <algorithm>#include <cstring>#include <cmath>#include <queue>#include <set>#include <vector>#define lson l,m,rt << 1#define rson m + 1, r, rt << 1 | 1#define havem int m = (l + r) >> 1#define maxn 100010using namespace std;int n,q;__int64 sum[maxn << 2],lazy[maxn << 2];void push_up(int rt){ sum[rt] = sum[rt << 1] + sum[rt << 1 | 1];}void build(int l,int r , int rt){ lazy[rt] = 0; if(l == r){ scanf("%I64d",&sum[rt]); return ; }havem; build(lson); build(rson); push_up(rt);}void push_dn(int rt,int m){ if(lazy[rt]){ lazy[rt <<1 ] += lazy[rt]; lazy[rt << 1 | 1] += lazy[rt]; sum[rt << 1|1] += lazy[rt] * (m >> 1); sum[rt << 1] += lazy[rt] * (m - (m >> 1) );//左孩子减 lazy[rt] = 0; }}void update(int L, int R, int cc, int l,int r, int rt){ if(L <= l && r <= R){ lazy[rt] += cc; sum[rt] += (__int64)cc * (r - l + 1); //sum[rt] += (__int64) lazy[rt] * (r - l + 1); return ; }havem; push_dn(rt,r - l + 1); if(L <= m) update(L,R,cc,lson); if(R > m) update(L,R,cc,rson); push_up(rt);}__int64 query(int L,int R,int l, int r, int rt){ if(L <= l && r <= R){ return sum[rt]; }havem; push_dn(rt,r - l + 1); __int64 ans = 0; if(L <= m) ans = query(L,R,lson); if(R > m) ans += query(L,R,rson); return ans;}int main(){ scanf("%d%d",&n,&q); build(1,n,1); int a,b,c; char op[10]; for(int i = 1; i <= q; i ++){ scanf("%s",op); if(op[0] == 'Q'){ scanf("%d%d",&a,&b); printf("%I64d\n",query(a,b,1,n,1)); }else{ scanf("%d%d%d",&a,&b,&c); update(a,b,c,1,n,1); } }return 0;}
树状数组的做法:(粘的)
一 算法
树状数组天生用来动态维护数组前缀和,其特点是每次更新一个元素的值,查询只能查数组的前缀和,
但这个题目求的是某一区间的数组和,而且要支持批量更新某一区间内元素的值,怎么办呢?实际上,
还是可以把问题转化为求数组的前缀和。
首先,看更新操作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的前缀和是不断变化的,可以用两个树状数组来维护。
树状数组的解法比朴素线段树快很多,如果把long long变量改成__int64,然后用C提交的话,可以达到1047ms,
排在22名,但很奇怪,如果用long long变量,用gcc提交的话就要慢很多。
代码是自己写的:
//区间更新,区间询问#include <iostream>#include <cstdio>#define maxn 100011using namespace std;typedef __int64 ll;ll a[maxn],d[maxn],id[maxn],sum[maxn];int Q,n;char op[10];void modify(int pos, ll val,ll* arr){ while(pos <= n){ arr[pos] += val; pos += (pos & -pos); }}ll getsum(int pos,ll* arr){ ll sum = 0; while(pos){ sum += arr[pos]; pos -= (pos & -pos); } return sum;}int main(){ scanf("%d%d",&n,&Q); for(int i = 1; i <= n; i ++){ scanf("%I64d",a + i); } a[0] = 0; for(int i = 1; i <= n; i ++){ sum[i] = sum[i - 1] + a[i]; } ll a,b,c; while(Q --){ scanf("%s",op); if(op[0] == 'Q'){ scanf("%I64d%I64d",&a,&b); ll ans = sum[b] - sum[a - 1]; ans += ((b + 1) * getsum(b,d) - getsum(b,id) ); ans -= ((a) * getsum(a - 1,d) - getsum(a - 1 ,id) );//a - 1 ~~ b printf("%I64d\n", ans); }else{ scanf("%I64d%I64d%I64d",&a,&b,&c); modify(a,c,d); modify(a,c * a,id); modify(b + 1,-c,d); modify(b + 1,-c * (b + 1),id); } } return 0;}
还有kzw版线段树,暂时还不会。。。
#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[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; 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; }
- poj 3468 很水的线段树lazy操作,为了理解hdu 3954 的 lazy做的。
- hdu 3954(线段树的特殊lazy操作)
- hdu -1698 很水的线段树 lazy
- hdu-4587-线段树的区间操作- lazy标记
- hdu 3954 线段树 Level up 很有特色的一个题(关于lazy操作)
- hdu4027(线段树的lazy操作)
- poj (3468)线段树lazy操作
- HDU 3954 线段树 特殊LAZY操作
- poj 3468 线段树lazy
- Poj 3468 线段树 lazy
- HDU 4027——线段树加奇怪的lazy
- coj 1123 带区间操作的线段树(lazy)
- 线段树lazy操作
- HDU 4578 线段树 多lazy操作
- POJ 3468 A Simple Problem with Integers(段更新的区间求和&Lazy思想&线段树)
- POJ 3667 Hotel(线段树的合并+lazy tag)【很详细!!】
- hdu 3954 Level up 线段树 升级版Lazy tag 区间整体的性质
- poj 3468(线段树+lazy思想)
- vbs操作excel全集
- Android利用RotateAnimation实现旋转变化动画
- android下的文件上传
- android原生widget 电量控制(PowerSave)设计浅析
- Windows7各版本功能区别(含图)
- poj 3468 很水的线段树lazy操作,为了理解hdu 3954 的 lazy做的。
- Java内存溢出
- android:布局参数,控件属性及各种xml的作用
- VMware的组网模式详解
- StreamTokenizer的用法
- 我离开了Autodesk
- Android build error
- PHP,分页函数封装成类
- linux sz rz