c++前缀和与差分

一、前缀和

1、简介

前缀和也是一个在比赛中比较实用的方法,他能很快得求出一个区间的和,速度为O(1)。

一维的前缀和数组sum[ i ]就是存的是前i个数的总和。根据这个公式我们很容易得到x到y区间的总和就是sum[ y ] - sum[ x ]。

二维的前缀和如sum[ i ][ j ]是指 (i,j)点的左上角的矩阵的各数总和,及0 – i行且0 – j列的和。他可以快速的求出矩阵中任何一个子矩阵的和,如求长为l且右下角顶点为(i,j)的正方形只需sum[ i ][ j ] - sum[ i ][ j - l ] - sum[ i - l ][ j ] + sum[ i - l ][ j - l ]。

2、代码模板
//1.一维的前缀和
for(int i = 1;i <= n;i++)
	sum[i] += sum[i-1];
	
ans = sum[y] - sum[x];
//2.二维的前缀和
for(int i = 1;i <= n;i++)
	for(int j = 1;j <= m;j++)
		sum[i][j] += sum[i-1][j] + sum[i][j-1] - sum[i-1][j-1]];

ans = sum[i][j] - sum[i-x][j] - sum[i][j-y] + sum[i-x][j-y];
3、经典例题

1.激光炸弹
思路:用二维的前缀和计算,求得矩阵的前缀和后枚举所有边长为r的情况,求出和的最大值即可。

#include<iostream>

using namespace std;
const int N = 5010;
int f[N][N];	//f为求sum和的二维数组
int n,r,h,l,x,y,w;	//n为点数,r为半径,h l为最大的长和宽

int main()
{
	cin >> n >> r;
	h = l = r;	//防止出现r比所有的点的坐标都大(整个地图都能炸上)
	for(int i = 0;i < n;i++)
	{
		cin >> x >> y >> w;
		x++,y++;	//从(1,1)开始,防止数组边界越界
		h = max(h,x);	//记录最大的横纵坐标
		l = max(l,y);
		f[x][y] = w;
	}

	for(int i = 1;i <= h;i++)	//求前缀和
		for(int j = 1;j <= l;j++)
			f[i][j] += f[i-1][j] + f[i][j-1] - f[i-1][j-1];

	int res = 0;
	for(int i = r;i <= h;i++)	//求任意矩阵的和,枚举所有边长为r的矩阵
		for(int j = r;j <= l;j++)
			res = max(res,f[i][j] - f[i-r][j] - f[i][j-r] + f[i-r][j-r]);
	cout << res << endl;	//res记录最大的矩阵和即为答案

	return 0;
}
4、用处

快速求一维或二维数组某区间的和


二、差分

1、定义

差分就是数组b对于数组a有b[ 1 ] == a[ i ],b[ i ] = a[ i ] - a[ i-1 ](2 <= i <= n),b就是a的差分。
差分和前缀和相反,在对数组某区间进行操作时十分方便。

2、代码模板
for(int i = 0;i < n;i++)
	cin >> a[i];
for(int i = n-1;i > 0;i--)	//从后往前循环
	a[i] -= a[i-1];		//差分覆盖a数组
3、经典例题

1.增减序列
思路:通过对区间加一减一使得数组的各数相同,就是指使数组的差分2-n位都变成0。对区间[ a,b ]的数加一就等于对数组的差分b[ a ] + 1且b[ b+1 ] - 1。
有了这个性质,我们就选b[ i ]和b[ j ]对i,j区间进行加或减的操作,为了使操作次数最小我们尽量选择一正一负作为i,j区间,这样一边加,一边减使得正负都接近0,操作次数就会减少,所以最少操作次数就是差分中2-n位的所有正数和与负数和的绝对值中的最大值。对于无法修改的数可以用b[ 1 ]或者b[ n+1 ]进行修改,结果数就是对正负数配对完后剩下的数加一。

#include<iostream>
#include<cmath>

using namespace std;
const int N = 100010;
int a[N],n;
long long int pos,neg;

int main()
{
	cin >> n;
	for(int i = 0;i < n;i++)
		cin >> a[i];
	
	for(int i = n-1;i > 0;i--)
		a[i] -= a[i-1];
	
	for(int i = 1;i < n;i++)
		if(a[i] > 0)
			pos += a[i];	//所有正数的和
		else
			neg -= a[i];	//所有负数的绝对值的和

	cout << max(pos,neg) << endl;
	cout << abs(pos-neg)+1 << endl;

	return 0;
}

2、最高的牛
思路:两头牛能够看见,则说明这两头之间的牛的身高最高为两边的减一,每次输入将中间的区间减一即可,但在输入后先判重,检查之前是否已经减过1了,就不用再次减一了。

#include<iostream>
#include<set>

using namespace std;
const int N = 10010;
int f[N],n,p,h,m;
set<pair<int,int> > judge;	//set判重,将两端点a,b压入pair

int main()
{
	int a,b;
	cin >> n >> p >> h >> m;
	f[1] = h;	//h为差分数组,此处意思是将原数组所有元素设为最大高度h
	for(int i = 0;i < m;i++)
	{
		cin >> a >> b;
		if(a > b)	//题目有a > b的情况
			swap(a,b);
		if(!judge.count({a,b}))	//如果之前没输过
		{
			judge.insert({a,b});	//标记
			f[a+1]--;	//差分数组的性质,区间[a,b]减一
			f[b]++;
		}
	}
	
	for(int i = 1;i <= n;i++)
	{
		f[i] += f[i-1];		//求出原数组
		cout << f[i] << endl;	
	}

	return 0;
}
4、用处

方便对某区间进行加减操作

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值