【算法笔记】差分与经典例题解析

1.差分定义与概念

差分和前缀和一样也是一种用空间换取时间的做法,这个算法可以让一段区间快速的加减掉一个数字,具体的做法也是预处理出一个差分数组,然后再对一段区间加减某个数的操作后还需要将这个预处理的数组还原。

1.1差分数组的创建

差分数组的创建方式有两种,假设原数组为a[N],差分数组为f[N]:

(1)利用原数组创建:

cpp 复制代码
f[i] = a[i] - a[i - 1];

(2)利用差分数组的性质:

cpp 复制代码
f[i] += a[i];
f[i + 1] -= a[i];

我们后面让区间加减某个数也是用的(2)的方式来执行的,因为原数组在执行任何操作前里面都是零,而我们的差分正是可以让区间加减某个数,那我们可以干脆直接用差分来创建差分数据更加的方便,因为我们不是什么时候都需要用到原数组的


1.2执行区间加减操作

我们假设区间的左端点为left, 有端点为right,我们需要在这个去区间加减的数字为k我们只需要:

cpp 复制代码
f[left] += k; f[right + 1] -= k;

很显然,当left == right 时就是我们第二种创建差分数组的方式


1.3差分数据的还原

差分数组的还原方式也有两种,一种是使用原数组的方式,一种是直接使用差分数据还原的方式

使用原数组:

cpp 复制代码
a[i] = a[i - 1] + f[i];

利用差分数组:

cpp 复制代码
f[i] = f[i - 1] + f[i]; 

这个是可以用通过定义用我们高中学过推到数列公式的方法证明的,我们会用就可以了


2.差分经典例题

差分和前缀和一样都是有一维和二维的不同题型的,我个人比较喜欢直接使用差分的方式,都可以根据自己的习惯就可以了。

2.1【模板】差分

这道题是一道模板题,核心代码已经在介绍差分时讲完了,所以我就不多BB了我这里提供两种写法:

利用原数组:

cpp 复制代码
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
int n,m;
LL a[N];
LL f[N];

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		cin >> a[i];
		f[i] = a[i] - a[i - 1];
	}
	while (m--)
	{
		int l, r, k; cin >> l >> r >> k;
		f[l] += k; f[r + 1] -= k;
	}
	for (int i = 1; i <= n; i++)
	{
		a[i] = a[i - 1] + f[i];
		cout << a[i] << ' ';
	}
	return 0;
}

利用差分数组:

cpp 复制代码
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
int n,m;
LL f[N];

int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		LL x; cin >> x;
		f[i] += x;
		f[i + 1] -= x;
	}
	while (m--)
	{
		int l, r, k; cin >> l >> r >> k;
		f[l] += k; f[r + 1] -= k;
	}
	for (int i = 1; i <= n; i++)
	{
		f[i] = f[i - 1] + f[i]; 
		cout << f[i] << ' ';
	}
	return 0;
}

2.2海底高铁

这道题长长的很吓人,其实算法原理很简单,因为如果我们发现这个人走过的路是确定的,所以我们可以把他走过的路视为一个区间,每当他走过这段路让这个区间加一,因为他走这段路所需的金额有两种计算方式,我们取一个最小值然后把所有段给累加起来就可以了:

cpp 复制代码
#include <iostream>
using namespace std;
const int N = 1e5 + 10;
typedef long long LL;
int n, m;
LL f[N];

int main()
{
	cin >> n >> m;
	int x; cin >> x;
	for (int i = 2; i <= m; i++)
	{
		int y; cin >> y;
		if (y > x)
		{
			f[x]++;
			f[y]--;
		}
		else
		{
			f[y]++;
			f[x]--;
		}
		x = y;
	}
	for (int i = 1; i <= n; i++)
	{
		f[i] = f[i - 1] + f[i];
	}
	LL ret = 0;
	for (int i = 1; i < n; i++)
	{
		int a, b, c; cin >> a >> b >> c;
		ret += min(a*f[i], c + b*f[i]);
	}
	cout << ret << endl;
	
	return 0;
}

2.3【模板】二维差分

当我们对一段区间加上k时,如果只在左端点加了k的话当还原差分数组时会发现我们对左端点到右边的整个数组都加了k,所有我们需要对右区间-k来平衡这种影响。因此我们可以把这个性质推广到二维上。

假设我们想在一个二维平面内加减某个数(k)时,设左上角为(x1, y1) 右下角为(x2, y2)根据上面的推广就有:

可以将x为i,y为j,k为x写成代码就是:

cpp 复制代码
void insert(int i1, int j1, int i2, int j2, LL x)
{
	f[i1][j1] += x; f[i1][j2 + 1] -= x;
	f[i2 + 1][j1] -= x; f[i2 + 1][j2 + 1] += x;
}

还原就是我们的二维前缀和:

cpp 复制代码
f[i][j] = f[i][j - 1] + f[i - 1][j] - f[i - 1][j - 1] + f[i][j];

整体的代码就是:

cpp 复制代码
#include <iostream>
using namespace std;
const int N = 1010;
typedef long long LL;
LL f[N][N];
int n,m,q;

void insert(int i1, int j1, int i2, int j2, LL x)
{
	f[i1][j1] += x; f[i1][j2 + 1] -= x;
	f[i2 + 1][j1] -= x; f[i2 + 1][j2 + 1] += x;
}

int main()
{
	cin >> n >> m >> q;
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			LL x; cin >> x;
			insert(i, j , i , j, x);
		}
	}
	while (q--)
	{
		LL x1, y1, x2, y2, k;
		cin >> x1 >> y1 >> x2 >> y2 >> k;
		insert(x1, y1, x2, y2, k);
	}
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			f[i][j] = f[i][j - 1] + f[i - 1][j] - f[i - 1][j - 1] + f[i][j];
			cout << f[i][j] << ' ';
		}
		cout << endl;
	}
	return 0;
}

2.4地毯

这道题和模板题几乎一摸一样,所以我这里就直接贴代码了:

cpp 复制代码
#include <iostream>
using namespace std;
const int N = 1010;
int f[N][N];
int n, m;

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

int main()
{
	cin >> n >> m;
	while (m--)
	{
		int x1, y1, x2, y2;
		cin >> x1 >> y1 >> x2 >> y2;
		insert(x1, y1, x2, y2);
	}
	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= n; j++)
		{
			f[i][j] = f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1] + f[i][j];
			cout << f[i][j] << ' ';
		}
		cout << endl;
	}
	return 0;
}

相关推荐
kronos.荒2 小时前
动态规划——整数拆分(python)
python·算法·动态规划
中屹指纹浏览器2 小时前
2026分布式多账号运营下指纹浏览器集群调度方案
经验分享·笔记
ACCELERATOR_LLC2 小时前
【DataWhale组队学习】DIY-LLM Task3 语言模型架构和训练的技术细节
人工智能·学习·语言模型·transformer
摇滚侠2 小时前
Java 零基础全套视频教程,面向对象(进阶),笔记 90-103
java·开发语言·笔记
say_fall2 小时前
红黑树底层原理全解析:从 5 大性质到 STL 容器底层实现
开发语言·c++
嵌入式小企鹅2 小时前
Kimi K2.6开源对标GPT-5.4、英飞凌AURIX拥抱RISC-V、工信部定调太空算力
人工智能·学习·开源·嵌入式·模型·半导体·昇腾
椰羊~王小美2 小时前
C、Java、Go、Python 对比
java·c语言
cici158742 小时前
基于Koopman模型预测控制的非线性流控制数据驱动框架
算法
6Hzlia2 小时前
【Hot 100 刷题计划】 LeetCode 416. 分割等和子集 | C++ 0-1背包 1D空间极致优化
c++·算法·leetcode