前缀和&&差分

算法笔记—关于前缀和和差分

前缀和

一维前缀和

首先理解和为前缀和?
如果有一个数组a[1] ,a[2],a[3],…a[N];
则他的前缀和数组
b[1] = a[1]
b[2] = a[1]+a[2]

b[i] = a[1] +a[2] + … + a[i];

即假设S[i]来表示数组a的前缀和
S[i] = S[i-1]+a[i];【等于前面的所有数的和加上现在这个位置的值即为当前的前缀和】
非常的简单可以说,所以这里简要带过

二维前缀和

即求几何上矩形范围类数据的和
怎么理解?我们不妨画图来理解一下
在这里插入图片描述
假设我们要求红色区域的前缀和
那么我们那么我们可以这么求
在这里插入图片描述
先加上这蓝色部分的矩形的元素

在这里插入图片描述
再加上这紫色部分的矩形的元素
最后,因为蓝色和紫色部分有重合的部分,所以我们要减去那多余的部分,也就是下图中绿色的区域
在这里插入图片描述
我们用S[i][j]来表示前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[i-1][j] +S[i][j-1]-S[i-1][j-1] - a[i][j] ;


差分

何为差分? 拿前缀和来作比较,上面的讲解中,S[n]为a[n]的前缀和数组,那么对应的,a[n]就为S[n]的差分数组,那么为什么要引入差分的概念呢?其实应用最多的就是用于区间修改与求和问题,比如,你要让S[n]的所有项都加上c,那么是不是正常做法要遍历数组,然后每个数都加上c?但是如果你有S[n]的差分数组a[n],你只要让a[0]+c即可,为何?因为S[n]的含义是数组a的前n项和,让a[0]+c相当于让每个S[i] 都加上了c,因为S的每一项都有a[0]这个因子,相当于只改变了a中的一个数据,让S数组的每个数据都发生了变化,非常的高效。

一维差分

一维数组如何获得差分数组?
假如b[n]为a[n]数组的差分数组,那必然有一个公式
b[i] = a[i] - a[i-1]; i!=0
b[0] = a[0];i=0;
可以说还是很好理解的,时刻记得把a[i]看成b的前i项和

int a[10] = { 1,2,3,4,5,6,7,8,9 ,10};
//构建差分数组
int b[10] = { 0 };
for (int i = 0; i < 10; i++)
{
	if (i == 0)
	{
		b[i] = a[i];
		continue;
	}
	b[i] = a[i] - a[i - 1];
}

构建完成后,b中的数据如下图
在这里插入图片描述
学了这个当然要立马应用一下

题目链接->区间修改求和题目
在这里插入图片描述
这里解释一下:如果做到让数组a的[l,r]范围的数据加或减去一个数?时刻记得,b[i]的改变会影响a[i]往后的每个数,但是我们要在a[r]的地方打住,所以我们让b[r+1] 减去这个数即可。
注意,下面并不为本题的AC代码,因为这道题还要用到线段树的知识,所以下面的代码仅能拿到差分数组能拿到的分,答案对,但会TLE。

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0)
const int N = 1e6 + 10;

//差分
//一维差分
int n, q;
int a[N];
int b[N];
signed main()
{
	IOS;
	cin >> n >> q;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		b[i] = a[i] - a[i - 1];
	}
	for (int i = 0; i < q; i++)
	{
		int command;
		cin >> command;
		if (command == 1)
		{
			int l, r, c;
			cin >> l >> r >> c;
			b[l] += c;
			b[r + 1] -= c;
		}
		else
		{
			int l, r,ans=0;
			cin >> l >> r;
			for (int i = 1; i <= n; i++)
			{
				a[i] = b[i] +a[i-1];
			}
			for (int i = 1; i <= n; i++)
			{
				b[i] = a[i] - a[i - 1];
			}
			for (int i = l; i <= r; i++)
				ans += a[i];
			cout << ans << endl;
		}
	}

	return 0;

}

二维差分

思考一下,如何构建二维差分?
公式
b[i][j] = S[i][j]-S[i-1][j]-S[i][j-1]+S[i-1][j-1];
公式怎么来的呢,记不记得上面二维前缀和求S[i][j]的公式?然后里面的a[i][j]就是S[n]的差分数组,移项即可得到。
我们还是来画图理解一下
在这里插入图片描述
我们要求这个红色部分的值,但我们现在已知的是前缀和数组的值,也就是每个大矩形的值,并不知道每个小方块的值是多少,所以我们要利用几何关系,通过大矩形之间的运算把这个小方块求出来。

我们先加上包括这个红色方块在内【紫色表示蓝色和红色重合了】的大矩形的所有元素
在这里插入图片描述
然后再减去这个紫色方块左边和上面的最大矩形
在这里插入图片描述
在这里插入图片描述
很显然,我们肯定有减多了的部分,也就是以下的黑色部分,要把它加回来
在这里插入图片描述

所以b[i][j]【紫色部分】 = S[i][j]【蓝色】-S[i-1][j]【绿色】-S[i][j-1]【粉色】+S[i-1][j-1]【黑色】;

那么我们求出差分数组以后,我们要怎么向一维差分一样改变b[i][j]的值来改变S的区间中的值呢?
比如,我要让Sx1,y1—Sx2,y2子矩形中的元素加上一个数,该怎么解决呢?首先你要知道的是,改变b[i][j]的值,随着改变的是S的S[i][j]以后的值,而不是以前的值了,改变以前的值那是前缀和,要区分开来。

举个例子:
在这里插入图片描述
我们要使这个蓝色区域都加上C,白色的部分不变,怎么处理呢?【左上角坐标x1,y1,右下角坐标x2,y2】

先让b[x1][y1]+=c;在图中的反映是这样的:
在这里插入图片描述
紫色区域【包括现在这个蓝色(我这里只是方便观看没把那个蓝色变成紫色)】全都加上了c,因为我们对b[i][j]的操作会影响到后面的每一个值。你很容易发现,加多了,把多的区域减去:

减去[x2+1,y1]【黄色】:
在这里插入图片描述

减去[x1,y2+1]:【粉色】
在这里插入图片描述
很容易看出来,我们减多了,再把减多的部分加回来

加上[x2+1][y2+1] 【灰色区域】
在这里插入图片描述
最后我们的目的就达到了,然后我们把这一段封装起来即可

void insert(int x1, int y1, int x2, int y2, int c)
{
	b[x1][y1] += c;
	b[x1][y2 + 1] -= c;
	b[x2 + 1][y1] -= c;
	b[x2 + 1][y2 + 1] += c;
}

ok,那么我们该如何初始化数组b,第一种就是那个公式法,但是如果我们也用插入呢?

	for(int i =1;i<=n;i++)
		for(int j =1;j<=n;j++)
			insert(i,j,i,j,a[i][j]);

这样写,是假设原来的a[i][j]是空的,然后我们通过对差分数组的操作让这个假想的空a[i][j]变得有值了,并且就是实际a[i][j]的值,也就是,我们在这个双重for循环后得到的数组b的前缀和数组正是我们输入的a[i][j],又因为一个前缀和数组对应一个差分数组,所以我们就已经把差分数组构建出来了。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值