• 基础算法-前缀和,差分


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

    定义

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

    前缀和,即是某一数列中,第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

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

  • 相关阅读:
    python命令行工具模块-click
    python项目代码打包成Docker镜像
    背包九讲
    秒杀项目的3个奇数问题:并发队列的选择,请求接口的合理设计,高并发下的数据安全
    java类加载过程
    索引失效
    java面试
    进程间通信
    HashMap在Jdk1.7和1.8中的实现
    十大排序算法
  • 原文地址:https://www.cnblogs.com/Uninstalllingyi/p/11413143.html
Copyright © 2020-2023  润新知