\(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;
}
二维前缀和与二维差分的板子题。可以暴力拆成\(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;
}
用差分维护每一段道路经过的次数,对于每一段道路,记经过次数为\(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;
}
二分答案\(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