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

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;
}

相关推荐
C+++Python9 分钟前
C 语言 动态内存分配:malloc /calloc/realloc /free
c语言·开发语言
cen__y14 分钟前
Linux11(网络编程)
linux·运维·服务器·c语言·网络·网络协议·tcp/ip
云栖梦泽在20 分钟前
AI安全入门:AI模型泄露的风险与防护措施
人工智能·算法·动态规划
水木流年追梦33 分钟前
大模型入门-应用篇3-Agent智能体
开发语言·python·算法·leetcode·正则表达式
小新同学^O^35 分钟前
简单学习 --> WebSocket
java·websocket·网络协议·学习
洛水水43 分钟前
【力扣100题】31.二叉树的层序遍历
算法·leetcode·职场和发展
君义_noip1 小时前
CSP-S 2025 入门级 第一轮(初赛) 完善程序(1)
c++·算法·信息学奥赛·初赛·csp 第一轮
洛水水1 小时前
【力扣100题】41.爬楼梯
算法·leetcode·职场和发展
蜡笔小马2 小时前
07.C++设计模式-组合模式
c++·设计模式·组合模式
sheeta19982 小时前
LeetCode 每日一题笔记 日期:2026.05.13 题目:1674. 使数组互补的最少操作次数
笔记·算法·leetcode