洛谷 P3368:树状数组2 ← 差分

【问题描述】
题目来源:树状数组 2 - 洛谷

已知一个数列,你需要进行下面两种操作:
1. 将某区间每一个数都加上 x;
2. 求出某一个数的值。

【输入格式】
第一行包含两个整数 N、M,分别表示该数列数字的个数和操作的总个数。
第二行包含 N 个用空格分隔的整数,其中第 i 个数字表示数列第 i 项的初始值。
接下来 M 行每行包含 2 或 4个整数,表示一个操作,具体如下:
操作 1: 格式:1 x y k 含义:将区间 [x,y] 内每个数加上 k;
操作 2: 格式:2 x 含义:输出第 x 个数的值。

【输出格式】
输出包含若干行整数,即为所有操作 2 的结果。

【输入输出样例】
输入:
5 5
1 5 4 2 3
1 2 4 2
2 3
1 1 5 -1
1 3 5 7
2 4

输出:
6
10

【数据规模与约定】
对于 30% 的数据:N≤8,M≤10;
对于 70% 的数据:N≤10000,M≤10000;
对于 100% 的数据:1≤N,M≤500000,1≤x,y≤n,保证任意时刻序列中任意元素的绝对值都不大于 2^30 。

【算法分析】
树状数组基本知识参见:树状数组介绍_hnjzsyjyj的专栏-CSDN博客
树状数组的经典操作是“单点更新,区间查询”。那么在遇到“洛谷P3368”等“将区间 [x,y] 内每个数加上 k,输出第 x 个数的值”等“区间更新,单点查询”的问题时,怎么办?这就需要利用差分的思想,将“单点更新,区间查询”问题转换为熟悉的“区间更新,单点查询”问题求解。
具体方案为:设原数组为 A[i],定义差分数组 D[i]=A[i]−A[i−1],便可将对数组A[]的“区间更新”操作转化为对差分数组D[]的两次“单点更新”操作。也就是说,此时要将差分数组D[]作为新的原数组构建新的树状数组并实现相关操作。则在新的树状数组中对差分数组D[]的特定“点更新”操作将等效于对原来的原数组A[]所要求进行的“区间更新”操作。要注意,树状数组的下标从1开始,则A[0]空置未用,故有 A[0]=0
同时,依据差分数组的定义 D[i]=A[i]−A[i−1] 可知,
D[1]=A[1]−A[0]
D[2]=A[2]−A[1]
D[3]=A[3]−A[2]
......
D[i]=A[i]−A[i-1]
上面各式子相加,可得D[1]+D[2]+D[3]+...+D[i]=A[i]-A[0],又由于A[0]=0,所以可得 A[i]=D[1]+D[2]+D[3]+...+D[i]
显然,利用上文结论 A[i]=D[1]+D[2]+D[3]+...+D[i] ,可将对数组A[]的“单点查询”操作转化为对差分数组D[]的“区间查询”操作。

下面给出一个具体实例,设数组A[]={1,7,3,6,8,5,9,2,10},依据上文所述具体方案,可得差分数组D[]={1,6,-4,3,2,-3,4,-7,8}。假如对数组A[]的区间[2,6]内的每个元素都加上2,则A[]数组变为A[]={1,9,5,8,10,7,9,2,10},差分数组则变为D[]={1,8,-4,3,2,-3,2,-7,8}。

仔细观察,发现“对数组A[]的区间[2,6]内的每个元素都加上2”这个操作执行后,对应的差分数组D[]中只有D[2]、D[7]的值发生改变。原因是,对数组A[]的区间[x,y]内的每个元素都加上k,将会使 A[x] 与前一个元素 A[x-1] 的差增加 k,A[y+1] 与 A[y] 的差减少 k,且  A[x] ~ A[y] 中其他相邻元素间的差值保持不变。所以,对数组A[]的区间[x,y]内的所有元素进行修改,只用修改D[x]与D[y+1]便可:D[x]=D[x]+k,D[y+1]=D[y+1]-k

显然,依据上述方法,便可将对数组A[]的“区间更新”操作转化为对差分数组D[]的两次“单点更新”操作。此操作需要用到树状数组“点更新”操作 update 的代码:
https://blog.csdn.net/hnjzsyjyj/article/details/120559543),相关代码内容如下:

int pre=0;
int val;
for(int i=1; i<=n; i++) { //下标从1开始
    scanf("%d",&val);
    update(i,val-pre); //构造差分数组D[]的树状数组
    pre=val;
}

update(x,k);
update(y+1,-k);


【算法代码】

#include <bits/stdc++.h>
using namespace std;

const int maxn=5e5+5;
int c[maxn]; //差分数组D[i]=A[i]-A[i-1]的树状数组
int n,m;

int lowbit(int i) {
    return (-i)&i; //返回每个c[i]所包含的元素个数
}

void update(int i,int val) { //点更新
    while(i<=n) {
        c[i]+=val;
        i+=lowbit(i);  // i的后继(父结点)
    }
}

int preSum(int i) { //前缀和
    int s=0;
    while(i>0) { //树状数组的下标从1开始
        s+=c[i];
        i-=lowbit(i); //i的前驱
    }
    return s;
}

int main() {
    scanf("%d%d",&n,&m);
    memset(c,0,sizeof(c));

    int pre=0;
    int val;
    for(int i=1; i<=n; i++) { //下标从1开始
        scanf("%d",&val);
        update(i,val-pre); //构造差分数组D[]的树状数组
        pre=val;
    }

    while(m--) {
        int k,u,v,w;
        scanf("%d",&k);
        if(k==1) {
            scanf("%d%d%d",&u,&v,&w);
            update(u,w);
            update(v+1,-w);
        } else {
            scanf("%d",&u);
            printf("%d\n",preSum(u));
        }
    }

    return 0;
}

/*
in:
5 5
1 5 4 2 3
1 2 4 2
2 3
1 1 5 -1
1 3 5 7
2 4

out:
6
10
*/



【参考文献】
https://www.luogu.com.cn/problem/P3368
https://blog.csdn.net/hnjzsyjyj/article/details/120559543
https://www.luogu.com.cn/problem/solution/P3368






 

Supongo que te gusta

Origin blog.csdn.net/hnjzsyjyj/article/details/120573850
Recomendado
Clasificación