前缀和,差分,二维前缀和,二维差分

前缀和

作用:区间查询

定义:前缀和数组predix[n],原数组a[n],其间的关系是

区间查询:求a[left]到a[right]之间的元素和:p[right] - p[left-1]

模板:

#include<iostream>
using namespace std;
#define int long long
const int N = 1e5+5;
int a[N];
int prefix[N];
signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	for(int i=1;i<=n;i++)
		prefix[i] = prefix[i-1] + a[i];
	int q;
	cin>>q;
	while(q--)
	{
		int l,r;
		cin>>l>>r;
		cout<<prefix[r] - prefix[l-1]<<'\n';
	}
}

差分

作用:区间修改

定义:差分数组d[n]和原数组a[n]的关系为:

d[i] = a[i] - a[i-1];

区间修改:让a[left]到a[right]之间的元素+x:d[left]+=x,d[right+1]-=x

例题:

//还原成原数组,进行后缀区间修改
/*
第一行输入n 1e5
第二行输入数组a +-1e9 
输入 m
接下来m行,输入l,r,x,让区间l和r中的元素都加x
接下来输入q
接下来q次查询,每次输入 l 和 r 
输入
5
1 1 1 1 1
2
1 3 1
2 5 -2
2
1 2
3 4
输出
2 
-1 
*/ 
#include<iostream>
using namespace std;
#define int long long
const int N = 1e5+5;
int a[N];
int predix[N];
int diff[N];
signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int n;
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i];
	for(int i=1;i<=n;i++)
		diff[i] = a[i] - a[i-1];
	int m;
	cin>>m;
	while(m--)
	{
		int l,r,x;
		cin>>l>>r>>x;
		diff[l]+=x;
		diff[r+1]-=x;
	}
	for(int i=1;i<=n;i++)
		a[i] = a[i-1] + diff[i];
	for(int i=1;i<=n;i++)
		predix[i] = predix[i-1] + a[i];
	int q;
	cin>>q;
	while(q--)
	{
		int l,r;
		cin>>l>>r;
		cout<<predix[r] - predix[l-1]<<'\n';
	}
}

前缀和,原数组,差分之间的关系

差分做前缀和操作变成原数组,原数组做前缀和操作变成前缀和数组

前缀和数组做差分操作变原数组,原数组做差分操作变差分数组

三者的关系类似于求导,原函数和积分


二维前缀和:

公式推导

p[3][3] = p[2][3]+p[3][2]-p[2][2]+a[3][2]

由此推断出

p[i][j] = p[i-1][j]+p[i][j-1]-p[i-1][j-1]+a[i][j]

利用二维前缀和数组进行区间查询

如图所示

求[3,3]到[5,5]之间的元素和ans

ans = p[5][5]-p[2][5]-p[5][2]+p[2][2]

由此推知

区间查询x1,y1到x2,y2的公式是

p[x2][y2]-p[x1-1][y2]-p[x2][y1-1]+p[x1-1][y1-1]

例题
/*
第一行n,m,q,n行m列q次询问
接下来输入数组
接下来q行每行x1,y1,x2,y2,分别表示左上角坐标和右下角坐标 
prefix[i][j] = prefix[i-1][j] + prefix[i][j-1] -prefix[i-1][j-1] + a[i][j]
输入:
7 3 2
3 5 1
6 2 4
7 9 10
4 3 6
3 9 9
6 10 1
9 10 4
2 2 7 3
2 1 4 2
输出:
77
31 
*/
#include<iostream>
using namespace std;
#define int long long
const int N = 1e3+5;
int a[N][N];
int prefix[N][N];
signed main()
{
	int n,m,q;
	cin>>n>>m>>q;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			cin>>a[i][j];
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			prefix[i][j] = prefix[i-1][j] + prefix[i][j-1] -prefix[i-1][j-1] + a[i][j];
	while(q--)
	{
		int x1,y1,x2,y2;
		cin>>x1>>y1>>x2>>y2;
		cout<<prefix[x2][y2] - prefix[x1-1][y2] - prefix[x2][y1-1] + prefix[x1-1][y1-1]<<'\n';
	}
}

二维差分

公式推导:
for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			diff[i][j] += a[i][j];
			diff[i+1][j] -= a[i][j];
			diff[i][j+1] -= a[i][j];
			diff[i+1][j+1] += a[i][j];
		}
	}
区间修改

如下图

diff[x1][y1]+=c;
diff[x1][y2+1]-=c;
diff[x2+1][y1]-=c;
diff[x2+1][y2+1]+=c;
例题
/*
给定n行m列矩阵
有q个操作,每个操作输入x1,y1,x2,y2,c,表示对应的子矩阵加c
输出完成操作后的矩阵 
输入:
4 3 3
1 5 1
3 3 2
5 3 4
4 4 2
1 2 1 2 2
2 1 2 3 2
4 2 4 3 1
输出 
1 7 1
5 5 4
5 3 4
4 5 3 
*/
#include<iostream>
using namespace std;
const int N = 1e3+5;
int a[N][N];
int diff[N][N];
int main()
{
	int n,m,q;
	cin>>n>>m>>q;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			cin>>a[i][j];
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
		{
			diff[i][j] += a[i][j];
			diff[i+1][j] -= a[i][j];
			diff[i][j+1] -= a[i][j];
			diff[i+1][j+1] += a[i][j];
		}
	}
	while(q--)
	{
		int x1,y1,x2,y2,c;
		cin>>x1>>y1>>x2>>y2>>c;
		diff[x1][y1]+=c;
		diff[x1][y2+1]-=c;
		diff[x2+1][y1]-=c;
		diff[x2+1][y2+1]+=c;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			a[i][j] = a[i-1][j] + a[i][j-1] - a[i-1][j-1] + diff[i][j];
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
			cout<<a[i][j]<<" ";
		cout<<'\n';
	}
}

例题:鼠鼠我鸭

题目概述:

农场有n只动物,要么是鼠鼠,要么是鸭鸭,从1到n编号,每只动物有个体重ai
可以至多一次使用魔法,让编号位于[l,r]内的鼠鼠变鸭鸭,鸭鸭变鼠鼠
请问鸭鸭的总体重最大是多少
第一行输入T,表示样例个数1~10
对每个样例
第一行输入n 1e5
第二行n个整数,表示第i个小动物的类型,0为鼠鼠,1为鸭鸭
第三行n个整数,表示第i个小动物的体重1e9
输入
2
3
0 0 0
1 2 3
4
0 1 0 0
2 5 6 5
输出
6
16

思路:

用ess记录不适用魔法的情况下的鸭鸭体重

再用prefix[]记录体重偏移量的前缀和

所谓体重偏移量就是如果对某只动物使用魔法会使鸭鸭的总体重增加多少或者减少多少

比如一只鼠鼠体重10kg,对他使用魔法, 变成了鸭鸭,这样鸭鸭的总体重会增加10kg,所以该鼠鼠的体重偏移量是+10

再比如一只鸭鸭体重9kg,对他使用魔法,变成了鼠鼠,这样鸭鸭总体重会减少9kg,所以该鸭鸭的体重偏移量是-9

最终只需找到体重偏移量前缀和的最大子段和,即找到一个left和right,使得prefix[right]-prefix[left-1]的值最大即可(枚举right,一层循环即可)

代码:
#include<iostream>
using namespace std;
const int N = 1e5+5;
int a[N],b[N];//类型,体重 
int prefix[N];//偏移量的前缀和 
void solve()
{
	int n;
	cin>>n;
	int ess=0;//如果不做魔法,鸭鸭的体重 
	for(int i=1;i<=n;i++)
		cin>>a[i];
	for(int i=1;i<=n;i++)
	{
		cin>>b[i];
		prefix[i] = prefix[i-1]+(a[i]?-1:1)*b[i];//如果是鼠鼠,就加上体重,否则减去 
		ess += a[i]*b[i];
	}	
	int maxa=0,mina=0;//prefix的最大子段和,在i之前最小的prefix[] 
	for(int i=1;i<=n;i++)
	{
		maxa = max(maxa,prefix[i]-mina);
		mina = min(mina,prefix[i]);
	}
	cout<<ess+maxa<<'\n';
}
int main()
{
	int t;
	cin>>t;
	while(t--)
		solve();	
} 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

owooooow

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值