一维差分加上二位差分

差分

一 差分综述

​ 对于差分来说,我们以一维举例,他解决的问题是在一个一维数组 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 得到差分数组,如果想要原数组 再去利用二维前缀和的公式去得到

  • 一般差分可以直接背公式套模板 尽量还是推一下 这样记忆力强

​ 二

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值