poj 3468 很水的线段树lazy操作,为了理解hdu 3954 的 lazy做的。

操作:

       对一个区间的每个数加上一个数。

询问:

      一个区间的和为多少。

     这让我想起那道操作是对一个区间的每个数根号,求的也是和。lazy得不同而已。做了几题lazy的题,发觉和别的结合起来的时候我又不懂了。于是重新开始做lazy的基础题。现在又对lazy有了更深的了解。

     我发觉我每次写的lazy都是自动写成了在本个区间就释放的lazy,但是没有仔细思考过为什么。

     如果,在本个区间不释放lazy,那么子区间的孩子的改变没有体现在父亲节点上,也不能用我写的push_up来体现(子区间没有释放,父亲节点已经释放了,不能用没有释放的去改变释放的)。假设区间[1,5],我们去改变[1,3],[1,3]的lazy值变成1,没有释放。但是我们下一次去查询[1,5]的时候,体现不了[1,3]的改变。

加了个油!

ps:当我今天知道树状数组这题的做法的时候,我刹那间觉得原来简单的题也可以摇身一变,变成不简单的题。。。

/*
Pro: 0

Sol:

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 100010
using 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 100011
using 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;  
}  



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值