浅谈简单前缀和与差分问题

\(Part1:\) 前缀和与差分的简单定义

考虑一个数组\(A\),其项数为\(n\)项。有\(m\)次询问,每次询问给定两个参数\(l\)和\(r\),要求求出\(A[l]+A[l+1]+...+A[r]\)。

怎么做呢?

  • 暴力:显然是\(O(nm)\)的
  • 数据结构维护:显然是\(O(mlogn)\)的
    前缀和的用处就在于可以将这样的序列区间求和的问题用\(O(n+m)\)的复杂度,在线性的时间和空间内解决出。

那么前缀和究竟是什么呢?

在读入\(A\)数组的时候,我们预处理出一个前缀和数组\(S\),满足\(S[i]=A[1]+A[2]+...+A[i]\)。如何快速计算\(S\)?显然有\(S[i]=S[i-1]+A[i]\),\(S\)数组就可以这样线性递推出来了。

那么对于每次查询,就有\(A[l]+A[l+1]+...+A[r]=S[r]-S[l-1]\),实现了\(O(n)\)预处理,\(O(1)\)回答单次查询。

那么什么信息可以用这样的“前缀”方式维护呢?只要满足可加可减性也就是满足结合律的信息,都可以这样快速计算维护。例如:快速求区间和,快速求区间积,快速求区间异或和等等。

那么差分又是什么呢?差分是前缀和的逆运算。

在读入\(A\)数组的时候,我们预处理出一个差分数组\(T\),满足\(T[i]=A[i]-A[i-1]\)。差分数组就是原数组相邻两项的差。

考虑对\(n\)项的数组\(A\)给出\(m\)次询问,每次询问给定三个参数\(l\)和\(r\)和\(d\),要求对\(A[l],A[l+1],...,A[r]\)均执行\(+d\)操作。最后求出\(A\)数组的每一项的值。

利用差分运算,对\(A[l],A[l+1],...,A[r]\)均执行\(+d\)操作,就等价于对差分数据\(T\)进行\(T[l]+=d,T[r+1]-=d\)的操作。

那么直接维护差分数组\(T\),对差分数组做前缀和即可还原出原数组\(A\)。

\(Part2:\) 二维前缀和与差分

和很多数据结构一样,前缀和与差分也可以推广到二维矩阵下。

二维前缀和:\(S[x][y]=S[x][y-1]+S[x-1][y]-S[x-1][y-1]+A[x][y]\)。就等价于一个容斥原理。

二维差分:对 \((x1,y1)\)~\((x2,y2)\)的 \(A\)数组\(+d\),等价于 \(T[x1][y1]+=d,T[x1][y2+1]?=d,T[x2+1][y1]?=d,T[x2+1][y2+1]+=d\)。知道\(T\)还原\(A\)做二维前缀和即可。

也可以将二维前缀和/二维差分,暴力拆成\(n\)个一维前缀和/一维差分进行运算。

\(Part3:\) 前缀和与差分的简单板子

P1115 最大子段和
最大子段和是一个经典问题,这里给出一种利用前缀和的线性做法。先预处理出一个前缀和数组\(S\)。

一个子段和\(A[l]+A[l+1]+...+A[r]\)就等价于\(S[r]-S[l-1]\)。要使得这个子段和最大,在维持\(S[r]\)为定值的同时,还要使得\(S[l-1]\)尽可能小。那么从左往右枚举右端点\(r\),并维护左侧的所有\(S\)中的最小值\(mins\)数组,两者相减即为此时的最大子段和,尝试更新答案。

#include<bits/stdc++.h>//P1115 最大子段和
using namespace std;
#define re register
#define ll long long
#define il inline
#define dou double
#define un unsigned
il int read()
{
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
#define INF 114514114
#define clr(x) memset(x,0,sizeof(x))
#define N 200000+10
int n,ans=-INF;
int a[N],s[N],mins[N];
int main()
{
    n=read();
    for(re int i=1;i<=n;i++)mins[i]=INF;
    for(re int i=1;i<=n;i++)
    {
        a[i]=read();
        s[i]=s[i-1]+a[i];
        mins[i]=min(mins[i-1],s[i]);
    }
    for(re int r=1;r<=n;r++)
        ans=max(ans,s[r]-mins[r-1]);
    cout<<ans<<endl;
    return 0;
}

P3397 地毯

二维前缀和与二维差分的板子题。可以暴力拆成\(n\)个一维前缀和/一维差分维护。

#include<bits/stdc++.h>//P3397 地毯
using namespace std;
#define re register
#define ll long long
#define il inline
#define dou double
#define un unsigned
il int read()
{
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
#define INF 114514114
#define clr(x) memset(x,0,sizeof(x))
#define N 1000+10
int n,m,a,b,c,d;
int tag[N][N],sum[N][N];
int main()
{
    n=read();m=read();
    for(re int i=1;i<=m;i++)
    {
        a=read();b=read();c=read();d=read();
        for(re int j=a;j<=c;j++)
        {
            tag[j][b]++;
            tag[j][d+1]--;
        }
    }
    for(re int i=1;i<=n;i++)
    {
        for(re int j=1;j<=n;j++)
        {
            sum[i][j]=sum[i][j-1]+tag[i][j];
            printf("%d ",sum[i][j]);
        }
        printf("\n");
    }
    return 0;
}

P3406 海底高铁

用差分维护每一段道路经过的次数,对于每一段道路,记经过次数为\(s\),那么它的最小花费就是\(min(as,bs+c)\)。即购买纸质票和购买IC卡的费用取最小值。注意每一段道路起点和终点的编号先后顺序可能需要调整。

#include<bits/stdc++.h>//P3406 海底高铁
using namespace std;
#define re register
#define ll long long
#define il inline
#define dou double
#define un unsigned
il int read()
{
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
#define INF 114514114
#define clr(x) memset(x,0,sizeof(x))
#define N 100000+10
#define M 100000+10
ll n,m,ans;
ll p[M],a[N],b[N],c[N],tag[N],s[N];
int main()
{
    n=read();m=read();
    for(re int i=1;i<=m;i++)p[i]=read();
    for(re int i=1;i<=n-1;i++)
    {
        a[i]=read();b[i]=read();c[i]=read();
    }
    for(re int i=1;i<=m-1;i++)
    {
        if(p[i]<p[i+1])
        {
            tag[p[i]]++;
            tag[p[i+1]]--;
        }
        else
        {
            tag[p[i+1]]++;
            tag[p[i]]--;
        }
    }
    for(re int i=1;i<=n;i++)s[i]=s[i-1]+tag[i];
    for(re int i=1;i<=n;i++)
        ans=ans+min(a[i]*s[i],b[i]*s[i]+c[i]);
    cout<<ans<<endl;
    return 0;
}

P1083 借教室

二分答案\(x\),只需判断\(x\)为答案是否合法。

如何\(check\)?用差分维护借教室情况,前缀和还原之后比较,判断是否合法。

#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define il inline
#define dou double
#define un unsigned
il int read()
{
    char c=getchar();int x=0,f=1;
    while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
    while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
    return x*f;
}
#define N 1000000+10
#define M 1000000+10
#define clr(x) memset(x,0,sizeof(x))
int n,m;
int a[N],d[M],s[M],t[M],tag[N],sum[N];
il bool check(int x)
{
    clr(tag);clr(sum);
    for(re int i=1;i<=x;i++)
    {
        tag[s[i]]+=d[i];
        tag[t[i]+1]-=d[i];
    }
    for(re int i=1;i<=n;i++)
    {
        sum[i]=sum[i-1]+tag[i];
        if(sum[i]>a[i])
            return false;
    }
    return true;
}
int main()
{
    n=read();m=read();
    for(re int i=1;i<=n;i++)
        a[i]=read();
    for(re int i=1;i<=m;i++)
    {
        d[i]=read();s[i]=read();t[i]=read();
    }
    int l=1,r=m;
    if(check(m))
    {
        cout<<0<<endl;
        return 0;
    }
    while(l<r)
    {
        int mid=(l+r)>>1;
        if(check(mid))
            l=mid+1;
        else
            r=mid;
    }
    cout<<-1<<endl<<l<<endl;
    return 0;
}

原文地址:https://www.cnblogs.com/Hakurei-Reimu/p/11621447.html

时间: 2024-08-30 08:05:59

浅谈简单前缀和与差分问题的相关文章

浅谈简单工厂模式和策略模式

1.简单工厂模式如图 代码: 缺点:简单工厂模式需要客户端认识两个类,Cash和CashFactory 优点:子类的实例化被工厂封装了起来,客户端看不到 2.策略模式如图 代码: public class Context{ Strategy strategy; public Context(Strategy strategy){ this.strategy = strategy; } public double getResult(double money){ return strategy.a

浅谈简单工厂,工厂方法,抽象工厂的使用

前言 这几天了解了工厂模式,自己也整理下思路,任何一种模式的出现都是为了让我们的程序有更好的可扩展性,工厂模式也不例外. 简单工厂 在实际的代码coding中我们在创建对象(也就是实例化一个类)的时候往往需要new class()这样来操作.举个例子: 这是项目结构 //这是一个中国人的类public class ChinesePepole { public void Show() { Console.WriteLine("I'm a {0}", this.GetType().Name

浅谈简单工厂与工厂方法

在园子混迹许久,每日看一些大神的佳作,深感受益匪浅,进而萌生了分享一些知识的想法.当然,作为一个屌丝程序员分享不了多么高大上的知识,只是把平时工作中积累的一些东西共享出来,希望大神们手下留情,不要拍的太狠.呵呵,闲言少叙,书归正传. 简单工厂和工厂方法   GOF创造了设计模式这个家族,为我们写出更面向对象的代码提供了便利.相对于这个家族几乎代代单传,工厂家族可谓是门丁兴旺,今天主要来体验下简单工厂和工厂方法.   这里要说一个题外话,简单工厂模式不属于GOF创造的23种设计模式,最多算一个临时

浅谈简单工厂,工厂方法,抽象工厂的区别和使用

工厂模式是分为三种,分别是简单工厂,工厂方法,抽象工厂.其中工厂方法和抽象工厂是GoF23种设计模式中的一种,而简单工厂则不是一种设计模式,更加可以理解的是一种编码时候预定俗称的一种习惯.那么,就在接下来三点中分别去分析理解工厂模式. 一 简单工厂:通过实例化一个工厂类,来获取对应的产品实例.我们不需要关注产品本身如何被创建的细节,只需要通过相应的工厂就可以获得相应的实例.简单工厂包括三种角色: 1.工厂:简单工厂模式的核心,它负责实现创建所有实例的内部逻辑.工厂类的创建产品类的方法可以被外界直

浅谈差分约束系统——图论不等式的变形

浅谈差分约束系统——图论不等式的变形 ----yangyaojia 版权声明:本篇随笔版权归作者YJSheep(www.cnblogs.com/yangyaojia)所有,转载请保留原地址! 一.定义 如若一个系统由n个变量和m个不等式组成,并且这m个不等式对应的系数矩阵中每一行有且仅有一个1和-1,其它的都为0,这样的系统称为差分约束( difference constraints )系统. 二.分析 简单来说就是给你n个变量,给m个形如x[i]-x[j]≥k①或x[i]-x[j]≤k②.求两

浅谈树上差分

浅谈树上差分 [引子] 我们遇到一些关于树的问题时,往往需要我们统计一些树上的信息,比如子树和,路径边覆盖.点覆盖(目前没见过别的类型).暴力的解法当然是遍历逐个点对其权值进行修改. 类比序列问题,其在进行区间修改时,可以用差分将\(O(n)\)复杂度降为\(O(1)\).在树上我们是对一条链进行处理,那差分在树上可不可用呢?答案是肯定的. [从序列到树] 在一个序列上进行差分的操作,相信各位都十分熟悉:假设当前我们要对一个序列的\(l\sim r\)区间的每个数执行\(+k\)操作,那么对于差

浅谈MD5及简单使用

原理简介: MD5即Message-Digest Algorithm 5(信息-摘要算法 第5版),用于确保信息传输完整一致.是计算机广泛使用的杂凑算法之一(又名:摘要算法.哈希算法),主流编程语言普遍已由MD5实现.将数据运算为另一固定长度值(十六进制的话:32位),是杂凑算法的基础原理,MD5的前身有MD2.MD3和MD4. MD5的作用是让大容量信息在用数字签名软件签署私人密钥前被"压缩"成一种保密的格式(就是把一个任意长度的字节串变换成一定长的十六进制数字串).除了MD5以外,

浅谈DevExpress&lt;五&gt;:TreeList简单的美化——自定义单元格,加注释以及行序号

今天就以昨天的列表为例,实现以下效果:预算大于110万的单元格突出显示,加上行序号以及注释,如下图: 添加行序号要用到CustomDrawNodeIndicator方法,要注意的是,取得的节点索引是从0开始的,所以要+1以便第一行从一开始算起. private void treeList1_CustomDrawNodeIndicator(object sender, CustomDrawNodeIndicatorEventArgs e) { TreeList tree = sender as D

浅谈SQL Server中的事务日志(三)----在简单恢复模式下日志的角色

浅谈SQL Server中的事务日志(三)----在简单恢复模式下日志的角色 本篇文章是系列文章中的第三篇,前两篇的地址如下: 浅谈SQL Server中的事务日志(一)----事务日志的物理和逻辑构架 浅谈SQL Server中的事务日志(二)----事务日志在修改数据时的角色 简介 在简单恢复模式下,日志文件的作用仅仅是保证了SQL Server事务的ACID属性.并不承担具体的恢复数据的角色.正如”简单”这个词的字面意思一样,数据的备份和恢复仅仅是依赖于手动备份和恢复.在开始文章之前,首先