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;  }  



原创粉丝点击