Poj 3468 A Simple Problem with Integers 相关的8种解法

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 long
int 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 long

int 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 long
int 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 long

LL 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 long

LL 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 long

LL 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;
	char		action;

    #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
    #endif

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

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

    #ifndef ONLINE_JUDGE
        freopen("in.txt","r",stdin);
    #endif

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

参考资料:

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


你可能感兴趣的:(Poj 3468 A Simple Problem with Integers 相关的8种解法)