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


原创粉丝点击