《算法竞赛进阶指南》0x03前缀和与差分

前缀和

对于一个数组A,他的前缀和数列S是通过递推能求出的基本信息之一:
S [ i ] = ∑ j = 1 i A [ j ] S[i]=\sum_{j=1}^{i}A[j] S[i]=j=1iA[j]
一个部分和,即数列A某个下标区间内的数的和,可表示为前缀和相减的形式:
s u m ( l , r ) = ∑ i = l r A [ i ] = S [ r ] − r [ l − 1 ] sum(l,r)=\sum_{i=l}^{r}A[i]=S[r]-r[l-1] sum(l,r)=i=lrA[i]=S[r]r[l1]
在二维数组中,可类似的求出前缀和,进一步求出部分和。

例题
acwing99.激光炸弹

对于A的二维前缀和:
S [ i , j ] = ∑ x = 1 i ∑ y = 1 j A [ x , y ] S[i,j]=\sum_{x=1}^{i}\sum_{y=1}^{j}A[x,y] S[i,j]=x=1iy=1jA[x,y]
有递推公式:
S [ i , j ] = S [ i − 1 , j ] + S [ i , j − 1 ] − S [ i − 1 , j − 1 ] + A [ i , j ] S[i,j]=S[i-1,j]+S[i,j-1]-S[i-1,j-1]+A[i,j] S[i,j]=S[i1,j]+S[i,j1]S[i1,j1]+A[i,j]
对于任意一个边长为R的正方形,有:

∑ x = i − R + 1 i ∑ y = j − R + 1 j A [ x , y ] = S [ i , j ] − S [ i − R , j ] − S [ i , j − R ] + S [ i − R , j − R ] \sum_{x=i-R+1}^{i}\sum_{y=j-R+1}^{j}A[x,y]=S[i,j]-S[i-R,j]-S[i,j-R]+S[i-R,j-R] x=iR+1iy=jR+1jA[x,y]=S[i,j]S[iR,j]S[i,jR]+S[iR,jR]

#include<iostream>
using namespace std;
#define MAX_N 10000
#define MAX_L 5000
int n,r;
int max_x,max_y;
int a[MAX_L+5][MAX_L+5];
int main()
{
	cin>>n>>r;
	max_x=5001,max_y=5001;
	for(int i=1,x,y,w;i<=n;i++)
	{
		cin>>x>>y>>w;
		a[x+1][y+1]+=w;
	}
	for(int i=1;i<=max_x;i++)
	for(int j=1;j<=max_y;j++)
	{
		a[i][j]+=a[i-1][j]+a[i][j-1]-a[i-1][j-1];
	}
	if(r>5000)
	{
	    cout<<a[5001][5001];
	    return 0;
	}
	int ans=0;
	for(int i=1;i<=max_x-r+1;i++)
	for(int j=1;j<=max_y-r+1;j++)
	{
		int xx=i+r-1,yy=j+r-1;
		ans=max(ans,a[xx][yy]-a[i-1][yy]-a[xx][j-1]+a[i-1][j-1]);
	}
	cout<<ans;
	return 0;
}

差分

对于一个给定的数列A,他的差分数列B定义为:
B [ 1 ] = A [ 1 ] , B [ i ] = A [ i ] − A [ i − 1 ] ( 2 ≤ i ≤ n ) B[1]=A[1],B[i]=A[i]-A[i-1](2\leq i\leq n) B[1]=A[1],B[i]=A[i]A[i1](2in)

前缀和与差分是一对互逆运算,差分序列B的前缀和序列就是原序列A,前缀和序列S的差分序列也是原序列A。

把序列A的区间[l,r]加d,其差分序列B的变化为B[l]加d,B[r+1]减d。

例题

acwing100.增减序列

对于差分序列b1b2…bn+1,我们每次可以选择两个,使其中一个+1,另一个-1,目的是把b2b3…bn全部变成0。
从b1b2…bn+1选两个数的方式有四种
1.选bi和bj,其中 2 ≤ i , j ≤ n 2 \leq i,j\leq n 2i,jn。这是一种需要尽可能多选策略,因为每次可以变化两个数。
2.选b1和bj,其中 2 ≤ j ≤ n 2\leq j\leq n 2jn
3.选bi和bn+1,其中 2 ≤ i ≤ n 2\leq i\leq n 2in
3.选b1和bn+1,这是一种没有意义的策略。
设正数总数为p,负数总数为q,按照先1后23的的原则,最后的最少操作次数为 m a x ( p , q ) = m i n ( p , q ) + ∣ p − q ∣ max(p,q)=min(p,q)+|p-q| max(p,q)=min(p,q)+pq
不同的情况数为 ∣ p − q ∣ |p-q| pq

#include<iostream>
using namespace std;
long long a[100005],b[100005];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    cin>>a[i];
    b[1]=a[1];
    long long k1=0,k2=0; 
    for(int i=2;i<=n;i++)
    {
    	b[i]=a[i]-a[i-1];
    	if(b[i]>0)k1+=b[i];
    	else k2-=b[i];
	}
    long long ans1=max(k1,k2);
    long long ans2=abs(k1-k2)+1;
	cout<<ans1<<endl<<ans2;
    return 0;
}

acwing101.最高的牛

为了让两只牛能看到,需要将他们之间的所有牛身高-1,但是注意需要用map维护相同的情况。

#include<iostream>
#include<set>
using namespace std;
#define MAX_N 10000
int n,p,h,m;
int c[MAX_N+5];
set<pair<int,int>>s;
int main()
{
	cin>>n>>p>>h>>m;
	for(int i=1,a,b;i<=m;i++)
	{
		cin>>a>>b;
		if(a>b)swap(a,b);
		if(s.find({a,b})!=s.end())continue;
		s.insert({a,b});
		c[a+1]-=1;
		c[b]+=1;
	}
	for(int i=1;i<=n;i++)
	{
		h+=c[i];
		cout<<h<<endl;
	}
	return 0;
}
  • 16
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值