差分

本质思想

构造一个 \(b\) 数组, 满足 \(a\) 数组是 \(b\) 数组的前缀和。
差分是前缀和的逆运算。

P2367 语文成绩

P2367 语文成绩 - 洛谷

暴力模拟过不了,时间复杂度是 \(\operatorname O(n^2)\)

差分思想

  • 对于数组 \(a\),定义 \(a\) 的差分数组为 \(b\),其中 \(b_1 = a_1, b_i = a_i - a_{i - 1}(2\leq i\leq n)\)
\(i\) 1 2 3 4 5 6 7 8
\(a_i\) 1 1 2 2 1 3 3 5
\(b_i\) 1 0 1 0 -1 2 0 2
  • 对数组 \(b\) 求前缀和:

    \[\sum_{i = 1}^n b_i = a_1 + \sum_{i = 2}^n(a_i - a_{i - 1}) = \sum_{i =1}^n a_i - \sum_{i = 1}^{n-1}a_i = a_n \]

  • 可以发现

    • 若将 \(b_x\) 增加 \(1\),再对 \(b\) 数组求前缀和得到 \(a\) 数组,那么得到的 \(a\) 数组中 \(a_x, a_{x+1},\dots ,a_n\) 均增加 \(1\)

    • 若将 \(b_y\) 减少 \(1\),再对 \(b\) 数组求前缀和得到 \(a\) 数组,那么得到的 \(a\) 数组中 \(a_y, a_{y+1},\dots ,a_n\) 均减少 \(1\)

    • 例如,对于一个全为 \(0\) 的差分数组 \(b\),若将 \(b_3\) 增加 \(1\),将 \(b_6\) 减少 \(1\),得到的 \(a\) 数组如表所示:

      \(i\) 1 2 3 4 5 6 7 8
      \(b_i\) 0 0 +1 0 0 -1 0 0
      \(a_i\) 0 0 1 1 1 0 0 0
  • 因此,将 \(a\) 数组中第 \(x\) 元素到第 \(y\) 元素增加 \(z\) 的修改操作,可以转化为将 \(b_x\) 增加 \(z\)\(b_{y + 1}\) 减少 \(z\),最后再对 \(b\) 数组求前缀和。

  • 这样,对于每次修改操作,只需要修改 \(2\) 个点,时间复杂度变为 \(\operatorname O(1)\)

  • 同时,因为只有在最后一次修改操作才需要得到 \(a\) 数组的具体值,所以只需要所有修改操作结束后求一次前缀和即可。

代码实现

#include<iostream>
#include<algorithm>
#include<cstdio>

const int N = 5e6 + 10;

int n, m, x, y, z, a[N], b[N];

int main()
{
 	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	std::cout.tie(nullptr);
	std::cin >> n >> m;
	for (int i = 1; i <= n; i++) std::cin >> a[i], b[i] = a[i] - a[i - 1];
	for (int i = 1; i <= m; i++)
	{
		std::cin >> x >> y >> z; b[x] += z, b[y + 1] -= z; 
	}
	int min = 0x7fffffff;
	for (int i = 1; i <= n; i++)
	{
		b[i] += b[i - 1]; min = std::min(min, b[i]);
	}
	std::cout << min;
	return 0;
}

P3397 地毯

P3397 地毯 - 洛谷

差分思想

设数组 \(a\) 的差分数组为 \(b\) ,其中 \(a_{x,y} = \sum_{i = 1}^x\sum_{j=1}^yb_{i,j}\),用前缀和知识可知:

\[b_{x,y} = a_{x,y} - a_{x-1,y} - a_{x,y-1} + a_{x-1,y-1} \]

截图20231211095101

\(b_{2,2}\) 增加 \(1\),再求二维前缀和,效果如图 \(a\rightarrow b\)

单独考虑一次覆盖操作,地毯左上角为 \((x_1,y_1)\),右下角为 \((x_2,y_2)\)。可以转化为:

  • \(b_{x_1,y_1},b_{x_2+1,y_2+1}\) 增加 \(1\)
  • \(b_{x_1,y_2+1},b_{x_2+1,y_1}\) 减少 \(1\)
  • 当地毯左上角为 \((2,2)\) 右下角为 \((3,3)\) 修改操作如图 \(c\rightarrow d\)

代码实现

#include<iostream>
#include<algorithm>
#include<cstdio>

const int N = 1e3 + 10;
int n, m;
int a[N][N];
int x1, y1, x2, y2;

int main()
{
 	std::ios::sync_with_stdio(false);
	std::cin.tie(nullptr);
	std::cout.tie(nullptr);
	std::cin >> n >> m;
	for (int i = 1; i <= m; i++)
	{
		std::cin >> x1 >> y1 >> x2 >> y2;
		a[x1][y2 + 1]--, a[x2 + 1][y1]--;
		a[x1][y1]++, a[x2 + 1][y2 + 1]++;
	}
	for (int i = 1; i <= n; i++, std::cout << std::endl)
	{
		for (int j = 1; j <= n; j++)
		{
			a[i][j] += a[i - 1][j] + a[i][j - 1] - a[i - 1][j - 1];
			std::cout << a[i][j] << " ";
		}
	}
	return 0;
}
posted @ 2023-12-11 09:35  加固文明幻景  阅读(21)  评论(0编辑  收藏  举报