差分学习笔记

文章引自 洛谷博客。

欢迎来到蒟蒻 $\texttt{Orange}$ 的差分学习笔记!

蒟蒻 $\texttt{Orange}$ 的第一篇笔记!(雾)

接下来是差分学习时刻!!

注:Orange 的 $ \LaTeX$ 很差,装饰的很丑,不要介意(

回答评论区

催更树上差分(

By Liu_Kevin

别太猛,等 n 年后我会了再来吧(

不能虐待蒟蒻(((

-f[c + 1][b], --f[a][d + 1]; 好像开头少了个减号(虽然基本没有影响...

By 追梦之鲸

感谢指出错误。 $\texttt{Orange}$ 已将其修正。/qq

树上差分不难的

不过我要O(n)-O(1)的(bushi

树上差分的话,O(nlogn)-O(1)还是可以的

By joy2010WonderMaker

都说了别太猛,怎么还有更猛的???(

算了,勉强接受吧,找时间更一下树上差分吧。。。。。 /kk

好厉害啊

By SiRiehn_nx

呜呜呜爷爷怎么fAKe啊 /kk

您一根头发都能吊打我 /kk

明明很菜啊 /ll

Update

$\texttt{2022.5.4}$ 修改了一处错误。

$\texttt{2023.1.10}$ 修改了一处错误。(感谢 @精神小伙!

0. 概念

前缀和的逆运算。

举个栗子,有个数列 $a = {2,3,5,6,4} $,我们把他进行一个工作:令 $b_i = a_i - a_{i - 1}$。这样我们可以得到数列 $b = {2,1,2,1,-2}$。这个 $b$ 数列就是数列 $a$ 的差分数列

上面说了,差分是前缀和的逆运算。于是,我们可以检验一下:

令数列 $c_i=b_i+b_{i-1}+\dots+b_1$,得到数列 $c={2,3,5,6,4}$,正好就是数列 $a$!惊不惊喜意不意外(

于是我们就知道差分是什么啦!

1. 用法

差分适用于多次区间修改,少次查询。查询时将差分数组前缀和一下就得到了被修改过后的原数组了。

2. 一维差分

直接举例:将区间 $[l,r]$ 每个数增加 $t$。

  • 多次区间修改

日常的暴力算法,需要 $O(n)$ 的时间。如下是日常暴力算法:

for (int i = l; i <= r; ++i) a[i] += t;

然鹅差分算法,可以利用前缀和的性质,变成 $O(1)$ 的时间效率:

f[l] += d, f[r + 1] -= d;

所以,多次修改的题目,非常适用于差分 差分非常适用于这种题。(语病

  • 不大适于单点修改

有的题目特殊,会抵消掉。

  • 适于少次查询

因为每次查询都需要前缀和,时间效率是 $O(n)$,查询多了就会爆。

  • 查询时前缀和

代码:

a[i] = a[i - 1] + f[i];

例题:P2367 语文成绩

差分模板走起来!

加一个取最小值就好!

#include <bits/stdc++.h>
using namespace std;

int n, q, x, y, z, a[5000010], f[5000010], ans;

int main() {
    cin >> n >> q;
    for (int i = 1; i <= n; ++i) cin >> a[i], f[i] = a[i] - a[i - 1];
    while (q--) {
        cin >> x >> y >> z;
        f[x] += z, f[y + 1] -= z;
    }
    ans = 0x3fffffff;
    for (int i = 1; i <= n; ++i) {
        a[i] = a[i - 1] + f[i];
        ans = min(ans, a[i]);
    }
    cout << ans;
}

3. 二维差分

就是,二维的差分(

首先我们试着求二维的差分数组。

以下面的数组为例:

1 2 1
4 5 2
7 8 3

(真的没办法弄 $\LaTeX$ 了……)

  1. 相邻两列做减法
1 2 1 => 1 1 -1
4 5 2 => 4 1 -3
7 8 3 => 7 1 -5
  1. 相邻两行做减法
1 2 1 => 1 1 -1 => 1 1 -1
4 5 2 => 4 1 -3 => 3 0 -2
7 8 3 => 7 1 -5 => 3 0 -2

于是就得到了差分数组!

还有一种更简单的方法:主对角线的和 $-$ 副对角线的和。

详见下面的图:

图很丑,能看就行(

红为主对角线,蓝为副对角线。

出了方格外的都是 $0$,所以数组要设大,减的时候还得减进去。

二维差分什么用呢?

显然, 二维的区间修改。

这样,把日常暴力算法的 $O(n^2)$ 的效率直接降到 $O(1)$。

++f[a][b], ++f[c + 1][d + 1];
--f[c + 1][b], --f[a][d + 1];

查询时前缀和即可。

f[i][j] += f[i - 1][j] + f[i][j - 1] - f[i - 1][j - 1];
cout << f[i][j] << " ";

例题:P3397 地毯

虽然说暴力可以过,但为了练习差分,还是要用差分的。(

直接上代码吧。(

#include <bits/stdc++.h>
using namespace std;

int n, m, a, b, c, d, f[1010][1010];

int main() {
    cin >> n >> m;
    for (int i = 1; i <= m; ++i) {
        cin >> a >> b >> c >> d;
        ++f[a][b], ++f[c + 1][d + 1];
        --f[c + 1][b], --f[a][d + 1];
        // O(1) 修改
    }
    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]; // 前缀和
            cout << f[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

然后就没有然后了。

4. 练习

更多精彩内容,敬请跳转至 Orange的做题笔记 查看 $\texttt{2022.5.1 - 2022.5.2}$ 的记录。

咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕咕

posted @ 2022-08-25 18:02  Orange_qwq  阅读(69)  评论(0)    收藏  举报