基础算法-前缀和,差分

我也不知道为什么我不会的大多是基础算法…

定义

对于一维来说,前缀和与差分的处理较为简单。

前缀和,即是某一数列中,第i号元素及其之前的全部元素之和。对于某数列A,其前缀和S的信息

差分。即第i项与i-1项之差。给定一个数列A,它的差分数列B为

 由此可以看出,前缀和和差分是一对互逆运算。

差分序列B的前缀和序列就是原序列A

前缀和序列S的差分序列就是原序列A

上面的性质非常之重要,稍后会在二维中用到。

应用

前缀和可以快速的帮助我们求得某一个区间的和。

即对于原序列A中的一个区间(l,r),有前缀和序列S

差分可以快速的帮助我们进行区间同时加、减操作。

对于原序列A的区间(l,r)加d 其差分序列就变化为

Bl+d ,Br+1-d

其他位置均不需要改变。如果需要实装到原序列上,只需要将差分序列做一个前缀和即可。

二维

 这里引用一个例题来讲解会显得比较自然

激光炸弹 BZOJ1218(前缀和)

题来

我们以S[i,j]代表二维前缀和。那么我们接下来观察一下

S[i,j],S[i-1,j],S[i,j-1],S[i-1,j-1]

 

 这样我们就可以得到这样的递推式

 

这是对于长度为1的小正方形的。我们可以由此推广到长度为R的正方形,并将公式进行移项,就可以得到以下公式

这样我们便解决了二维前缀和的问题。

有了这个方法,我们就可以枚举边长为R的正方形的右下角坐标(i,j),就可以通过公式计算出所有目标价值之和,取最大值即可。

听说这道题的空间卡的很恶心,这里我们不做讨论(因为这里不是题解)

现代艺术 NKOJ(差分)

这题是南开培训的时候的一道考试题,不知道是不是自己出的,这里就不追究题目来源了。

对于这道题,我们只需要找出覆盖部分的数字。比较容易想出,如果两个数字矩阵相互覆盖,那么重复部分(被覆盖部分)上显示的数字就一定不可能是第一个数字。也就是说,我们需要求出所有矩阵交叉位置的数字,并将其排除。如果1——N2中某数字从未出现,那么他就有可能是第一位数字(被覆盖了)。但是这里需要一个特殊判断,当场上只有一个数字(除去0),那么答案就固定为N2-1,因为其他数字都已经被覆盖了。

刚开始接触这道题的时候我认为是个扫描线问题,但我没打出它的写法,这里不做讨论。

对于每一个数字,我们需要得到它的四个顶点,以来确定这个数字矩阵。

那么对于每一个数字矩阵,我们将其覆盖的范围+1。再次扫描时,如果某个点上的统计数字>1,那么就代表其又覆盖成份,其不可能成为第一个填入的数字。

如果我们暴力枚举来进行矩阵区间加法操作,那么一定会超时。根据差分的性质,我们完全可以用差分来进行区间操作,最后通过差分系列的前缀和来还原原序列。

以下是这道题的代码(因为何老板加强了数据,我没仔细听他要求用的算法是什么,所以这道题我保持了差分做法,得分80分,扣分原因是MLE)

#include<iostream>
#include<cstdio>
#include<vector> 
using namespace std;
const int MAXN=1011;
const int MAXNN=1000010;
int n;
vector<int>A;
int nn;
inline int read(){
   int s=0,w=1;
   char ch=getchar();
   while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
   while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
   return s*w;
}
struct Cube{
    int x1;
    int x2;
    int y1;
    int y2;
}Cubes[MAXNN];
int Map[MAXN][MAXN];
int S[MAXN][MAXN];//差分序列
bool Appear[MAXNN];
bool Used[MAXNN];
int Appears;
int Orig[MAXN][MAXN];//原序列。原序列=差分序列的前缀和序列 
int ans;
int main(){
    n=read();
    nn=n*n;
    int Max=n+10;
    for(int i=1;i<=nn;i++){
        Cubes[i].x1=Max;
        Cubes[i].y1=Max;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            Map[i][j]=read();
            if(!Map[i][j]){
                continue;
            }
            if(!Appear[Map[i][j]]){
                Appear[Map[i][j]]=true;
                A.push_back(Map[i][j]);
                Appears++;
            }
            Cubes[Map[i][j]].x1=min(i,Cubes[Map[i][j]].x1);
            Cubes[Map[i][j]].x2=max(i,Cubes[Map[i][j]].x2);
            Cubes[Map[i][j]].y1=min(j,Cubes[Map[i][j]].y1);
            Cubes[Map[i][j]].y2=max(j,Cubes[Map[i][j]].y2);
        }
    }
    if(Appears==1){
        printf("%d",nn-1);
        return 0;
    }
    for(int j=0;j<A.size();j++){
        int i=A[j];
        S[Cubes[i].x1][Cubes[i].y1]++;
        S[Cubes[i].x2+1][Cubes[i].y1]--;
        S[Cubes[i].x1][Cubes[i].y2+1]--;
        S[Cubes[i].x2+1][Cubes[i].y2+1]++;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            Orig[i][j]=S[i][j]+Orig[i-1][j]+Orig[i][j-1]-Orig[i-1][j-1];
            if(!Used[Map[i][j]]&&Orig[i][j]>1){
                Used[Map[i][j]]=true;
                nn--;
            }
        }
    }
    printf("%d",nn);
}

我们从代码中提取到二维差分的公式:

对于区间修改操作,只需要将其左下角+k,右下角(x2+1,y1)-k。左上角-k,右上角+k

再通过二维前缀和公式,将差分序列做前缀和,得到原序列。

posted @ 2019-08-26 17:31  L1ngYi  阅读(682)  评论(0编辑  收藏  举报