文章目录
差分
一 差分综述
对于差分来说,我们以一维举例,他解决的问题是在一个一维数组 a[N]当中,我们任务的需求是一次在一个区间【l,r】(1<=l<=r<N)当中一次性对这整个区间同时加一个数或者减去一个数字。
对于这种问题的一般做法,我们通常是一个for循环然后把这个区间上的所有数字都加上或者减去一个数字,但是这样如果l=1,r=N的化时间复杂度为O(n),是非常低效的。但是,我们的差分方法可以保证在O(1)的操作当中完成我们的操作,相对来说比较简单。
下面介绍一维差分和二维差分。
二 一维差分
1.一维差分解释
对于一维差分,原数组不妨设为a[i](但是其实我们并不会使用这个a[i],只是为了好表述),差分数组不妨设为c[N],c[i]并不表示具体的意义,但是c[i]的前缀和等于a[i]。
明确了数组的含义之后,这个做法就可以很清晰的表述出来了,比如,我们要在【l,r】区间当中减去一个数字temp,代码模板如下。
void insert(int l,int r,int temp){
c[l]-=temp;
c[r+1]+=temp;
}
在这个代码下,比如我们要求a[l]到a[r]之间的任何一个数字从c[x],那么我们求c[x]的前缀和得到的数字肯定都是减去l的,然后[1,l-1]区间和[r+1,n]区间可以自行分析。
这样我们就可以实现一维的差分,然后的话一维差分 的应用见如下链接
2.相关例题
洛谷 P4552 [Poetize6] IncDec Sequence
https://www.luogu.com.cn/problem/P4552
这个题目的很明确的给出了要在某一个区间当中同时加减一个数字,我们可以试试考虑差分 。
这个题目要求让我们把这个数组当中所有数都变成一样的,我们可以换一个思路,就是差分数组除了第一个数是一个确定的值,其余的值都必须是0,假设第i-1不是0,那么我们利用前缀和求解i项的前缀和**a[i]**就一定和a[i-1]不相同。
然后这个题目就简单了,第一问,我们可以这么想,我们对一个区间进行+1或者-1操作其实就是执行了insert操作,这个操作有加就有减,所以对于某个区间c[l]到c[r+1]如果c[l]和c[r+1]是异号的,从全局来看,那么一定是他们两个抵消(也就是负数+1,正数减1)的时候要用到的次数最少。
然后,把异号处理完毕之后,就是所有的都是正数或者负数,那么就只能从第0项出发去考虑,因为如果c[l]是正数,c[r+1]也是整数,如果再用他们去尽可能使得c[l]和c[r+1]归0,很明显相当于没做(因为有加就有减),所以,我们就要从第0项出发去做,让l固定为0,r+1表示那些不为0的数字。这样就能在最小的步数之中找到答案。
然后,思考第二问,我们可以这么想,既然c[2]到c[n]都是0,那么差异就在最后c[1]是个什么形态,这个差异就是我们上面只是把l固定为了1,但是r+1也可以固定,r+1如果等于n+1的话,是不会改变我们的c[1]的。
然后,就有可能怀疑我们的r+1可以取到n+1吗,是可以的 我们举一个例子 insert(1,n,temp)表示1到n我们都减去temp,我们会在c[1]位置减去temp ,c[n+1]加上temp,c[n+1]我们在求前缀和的时候不会用到,但是仔细想一下只有这样才能在这整个区间上减去temp。
所以,对于第二问,c[1]的范围就是[c[1],c[1]+abs(p-q)],我们的任务,就是去求在这个区间里面有多少个数字。自然是abs(p-q)+1;
综上,这个题目结束。
代码如下:
//P4552代码
#include<iostream>
using namespace std;
typedef long long LL;
const int N=1e5+100;
int n;
LL c[N];
void insert(LL l,LL r,LL temp)
{
c[l]+=temp;
c[r+1]-=temp;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
LL temp;
cin>>temp;
insert(i,i,temp);
}
LL p=0,q=0;
int temp=c[1];
for(int i=2;i<=n;i++)
{
if(c[i]>0)
{
p=p+c[i];
}
else
{
q=q-c[i];
}
}
LL ans=max(p,q);
// 最后的这个数最小就是c[i]就是不变 最大就是p-q剩余的数字加上c[i] p-q就是那些相互抵消的,剩下的那些可以从第一个加,也可以加到最后一个上
// 就是求 c[i]+abs(p-q)和c[i]之间有多少个数字了
LL ans_1=abs(p-q)+1;
cout<<ans<<endl<<ans_1<<endl;
return 0;
}
三 二维差分
1.二维差分模板
对于二维差分来说同理,aij表示原数组当中的数据,cij表示差分数组当中的数据 cij的前缀和表示数组的原值aij,比如我们改变cij的值,改变的其实是cij及其右下角的内容,因为我们一旦对i和j右下角的区域做前缀和,那么cij一定会被加到。
所以 如果我们想对某一个区域的值整体改变,可以用如下的代码模板 其中 左上角坐标是(x1,y1),右下角坐标是(x2,y2)
void insert(int x1,int y1,int x2,int y2,int l)
{
c[x1][y1]+=l;
c[x2+1][y2+1]+=l;
c[x1][y2+1]-=l;
c[x2+1][y1]-=l;
}
可以按照如图3-1所示 的方式去理解 紫色的线代表改变cx1y1之后对于其右下角的影响 绿色的线代表减去cx2+1,y1对于右下角的影响
cx1,y2+1同理,cx2+1y2+1我没有画出,因为比较乱,但是从这张图片里可以清晰的看出重复减掉了那一部分。
2.经典的例题
洛谷P3397地毯
https://www.luogu.com.cn/problem/P3397
没有什么好说的,典型的板子题目 直接背板子就完事了!
对于这个差分的还原其实就是二维前缀和 。
coding:
#include<iostream>
#include<cstring>
using namespace std;
const int N=1e3+100;
// 二维度 差分解题 也是先初始化数组 之后再 求前缀和还原原数组
int a[N][N];
int n,m;
void insert(int x1,int y1,int x2,int y2,int l)
{
a[x1][y1]+=l;
a[x1][y2+1]-=l;
a[x2+1][y1]-=l;
a[x2+1][y2+1]+=l;
}
int main()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int x1,y1,x2,y2;
cin>>x1>>y1>>x2>>y2;
insert(x1,y1,x2,y2,1);
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++)
{
a[i][j]=a[i][j]+a[i-1][j]+a[i][j-1]-a[i-1][j-1];
cout<<a[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
四:注意的点
然后在差分当中需要注意的点:
-
一般差分数组都是从输入就开始insert 得到差分数组,如果想要原数组 再去利用二维前缀和的公式去得到
-
一般差分可以直接背公式套模板尽量还是推一下 这样记忆力强
二