差分的应用

前言

  之前一直不知道差分数组的本质含义,只知道差分数组是用来处理这样一种情况的,就是当需要给原序列$\left[ {l, r} \right]$这个区间范围内加上一个数$c$是,并不需要从$l$循环到$r$来给每个数加上$c$,只需要对原序列对应的差分数组$diff$进行$diff \left[ l \right] \mathrel{+}= c,~ diff \left[ {r+1} \right] \mathrel{-}= c$就可以了,之后再对差分数组求前缀和就可以得到修改后的序列。

  事实上差分数组本身的含义是两个相邻数值的差值,比如有原序列$a$和对应的差分序列$diff$,那么$diff \left[ i \right]$就表示$a \left[ i \right]$与$a \left[ {i-1} \right]$相差的大小,即$diff \left[ i \right] = a \left[ i \right] - a \left[ {i-1} \right]$。一个序列的差分序列就是这样子构造出来的,当$i = 1$,$diff \left[ i \right] = a \left[ i \right]$;当$i > 1$,$diff \left[ i \right] = a \left[ i \right] - a \left[ {i-1} \right]$,即:

\begin{cases}
diff \left[ 1 \right] = a \left[ 1 \right] \\
diff \left[ 2 \right] = a \left[ 2 \right] - a \left[ 1 \right] \\
diff \left[ 3 \right] = a \left[ 3 \right] - a \left[ 2 \right] \\
~~~~~~~~~~~~~~\vdots \\
diff \left[ n \right] = a \left[ n \right] - a \left[ {n-1} \right] \\
\end{cases}

  可以发现,$a \left[ i \right] = diff \left[ 1 \right] + diff \left[ 2 \right] + \dots + diff \left[ i \right]$,即对差分数值求前缀和就会得到原序列。

  因为没意识到这点,所以下面的题完全没有思路。

 

增减序列

给定一个长度为 $n$ 的数列 $a_{1},a_{2}, \dots, a_{n}$,每次可以选择一个区间 $\left[ {l,r} \right]$,使下标在这个区间内的数都加一或者都减一。

求至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列可能有多少种。

输入格式

第一行输入正整数 $n$。

接下来 $n$ 行,每行输入一个整数,第 $i+1$ 行的整数代表 $a_{i}$。

输出格式

第一行输出最少操作次数。

第二行输出最终能得到多少种结果。

数据范围

$0 < n \leq {10}^{5}$,
$0 \leq a_{i} < 2147483648$

输入样例:

4
1
1
2
2

输出样例:

1
2

 

解题思路

  从差分的角度来思考这个问题,我们要把原序列中的所有数变得一样,意味着对应的差分数组在$diff \left[ 2 \right]$到$diff \left[ n \right]$应该都为$0$,即$diff \left[ i \right] = 0, i \in \left[ {2,n} \right]$,因为按照差分数组的定义,如果差分数组为$0$,意味着两个相邻的数的差应该为$0$,即这两个数相等。比如$diff \left[ 2 \right] = 0$,说明$a \left[ 2 \right]$和$a \left[ 1 \right]$的差为$0$,即$a \left[ 2 \right] = a \left[ 1 \right]$,以此类推,一直到$diff \left[ n \right]$。$diff \left[ 1 \right]$可以是任意值,并且最后整个序列是由$diff \left[ 1 \right]$决定的(整个序列中的数都是$diff \left[ 1 \right]$)。

  因此整个问题变成了,最少操作多少次,使得$diff \left[ 2 \right]$到$diff \left[ n \right]$全部变成$0$,以及在最小操作的前提下,$diff \left[ 1 \right]$有多少种不同的值。

  我们原本是要对原序列的$a \left[ l \right]$到$a \left[ r \right]$都加上$1$或$-1$,现在变成了只需对$diff \left[ l \right] \pm 1$和$diff \left[ {r+1} \right] \mp 1$。

  并且由于序列的下标是$1 \sim n$,$r$最大取到$n$,因此差分数组有$n+1$项,从$diff \left[ 1 \right]$到$diff \left[ {n+1} \right]$。我们的目的是让$diff \left[ 2 \right]$到$diff \left[ n \right]$全部变成$0$,其中$diff \left[ 1 \right]$和$diff \left[ {n+1} \right]$可以是任意的值。下面我们来看一下不同的操作对差分数组$diff$的影响。

  以下$l$和$r$表示对原序列$a \left[ l \right]$到$a \left[ r \right]$都加上$1$或$-1$。

  • 当$2 \leq {l,r} \leq n-1$

  表示令$diff \left[ l \right] \mathrel{+}= 1,~ diff \left[ {r+1} \right] \mathrel{-}= 1$,或$diff \left[ l \right] \mathrel{-}= 1,~ diff \left[ {r+1} \right] \mathrel{+}= 1$。即在$diff \left[ 2 \right]$到$\left[ n \right]$中令一个数加$1$,另外一个数$-1$。

  • 当$l = 1,~ r \leq n-1$

  表示令$diff \left[ 1 \right] \mathrel{+}= 1$,$diff \left[ 2 \right]$到$diff \left[ n \right]$中的某个数$-1$;或$diff \left[ 1 \right] \mathrel{-}= 1$,$diff \left[ 2 \right]$到$diff \left[ n \right]$中的某个数$+1$。即让$diff \left[ 2 \right]$到$diff \left[ n \right]$中的某个数$\pm 1$。

  • 当$l \geq 2,~ r = n$

  表示令$diff \left[ {n+1} \right] \mathrel{+}= 1$,$diff \left[ 2 \right]$到$diff \left[ n \right]$中的某个数$-1$;或$diff \left[ {n+1} \right] \mathrel{-}= 1$,$diff \left[ 2 \right]$到$diff \left[ n \right]$中的某个数$+1$。即让$diff \left[ 2 \right]$到$diff \left[ n \right]$中的某个数$\pm 1$。

  • 当$l =1,~ r = n$

  表示对原序列的每个数都加上同一个数,无意义。

  因此最少用前$3$种的操作多少次,使得$diff \left[ 2 \right]$到$diff \left[ n \right]$全部变成$0$?我们会贪心地想,如果差分数组中既存在正数和负数,那么我们应该用第一种操作同时让正数和负数都减少$1$个,直到差分数组中只存在正数或负数,这时就选第$2$个操作或第$3$个操作。

  假设差分数组中所有正数的和为$p$,所有负数的和的绝对值为$q$,按照上面的贪心做法,那么最小的操作次数为$min\{ {p,q} \} + \left| {p-q} \right| = max \{ {p, q} \}$。

  我们先求理论最小值是多少,假设有$p>q$,因为我们每次的操作最多让$p$减少$1$个,因此要让$p$变为$0$,至少要$p$次操作,即最少次数应该$\geq p$,又因为我们上面构造了一种方法使得操作的次数恰好为$p$,因此这种贪心的做法是正确的。如果$p<q$,同理。

  最后一个问题是可以得到多少种不同的序列。前面已经分析过整个序列是由$diff \left[ 1 \right]$决定的,因此我们应该分析在最小操作次数的前提下,$diff \left[ 1 \right]$会被改变多少次。又因为当$p$和$q$同时大于$0$时都会用第$1$种操作,且第$1$种操作不会对$diff \left[ 1 \right]$产生影响,因此我们应该看当$p$和$q$有一个为$0$情况。此时还需要进行的$\left| {p-q} \right|$次操作$2$或操作$3$,且只有操作$2$会改变$diff \left[ 1 \right]$。我们可以选择的操作$2$的次数有$0, 1, 2, \dots, \left| {p-q} \right|$次(当然,剩下的操作次数应该分配给操作$3$),因此$diff \left[ 1 \right]$有$\left| {p-q} \right| + 1$种取值,即可以得到的不同的序列有$\left| {p-q} \right| + 1$种。

  AC代码如下:

 1 #include <cstdio>
 2 #include <cmath>
 3 #include <algorithm>
 4 using namespace std;
 5 
 6 const int N = 1e5 + 10;
 7 
 8 int a[N];
 9 
10 int main() {
11     int n;
12     scanf("%d", &n);
13     for (int i = 1; i <= n; i++) {
14         scanf("%d", a + i);
15     }
16     
17     // 求原序列的差分数组,要逆着求,因为顺着求时,a[i-1]已经被改变了
18     for (int i = n; i; i--) {
19         a[i] -= a[i - 1];
20     }
21     
22     long long p = 0, q = 0;
23     for (int i = 2; i <= n; i++) {
24         if (a[i] > 0) p += a[i];    // 求差分数组中正数的和
25         else q -= a[i];     // 求差分数组中负数的绝对值的和
26     }
27     
28     printf("%lld\n%d", max(p, q), abs(p - q) + 1);
29     
30     return 0;
31 }

 

空调

Farmer John 的 $N$ 头奶牛对他们牛棚的室温非常挑剔。

有些奶牛喜欢温度低一些,而有些奶牛则喜欢温度高一些。

Farmer John 的牛棚包含一排 $N$ 个牛栏,编号为 $1 \dots N$,每个牛栏里有一头牛。

第 $i$ 头奶牛希望她的牛栏中的温度是 $p_{i}$,而现在她的牛栏中的温度是 $t_{i}$。

为了确保每头奶牛都感到舒适,Farmer John 安装了一个新的空调系统。

该系统进行控制的方式非常有趣,他可以向系统发送命令,告诉它将一组连续的牛栏内的温度升高或降低 $1$ 个单位——例如「将牛栏 $5 \dots 8$ 的温度升高 $1$ 个单位」。

一组连续的牛栏最短可以仅包含一个牛栏。

请帮助 Farmer John 求出他需要向新的空调系统发送的命令的最小数量,使得每头奶牛的牛栏都处于其中的奶牛的理想温度。

输入格式

输入的第一行包含 $N$。

下一行包含 $N$ 个非负整数 $p_{1} \dots p_{N}$,用空格分隔。

最后一行包含 $N$ 个非负整数 $t_{1} \dots t_{N}$。

输出格式

输出一个整数,为 Farmer John 需要使用的最小指令数量。

数据范围

$1 \leq N \leq {10}^{5}$,
$0 \leq p_{i},t_{i} \leq 10000$

输入样例:

5
1 5 3 3 4
1 2 2 2 1

输出样例:

5

样例解释

一组最优的 Farmer John 可以使用的指令如下:

初始温度     :1 2 2 2 1
升高牛棚 2..51 3 3 3 2
升高牛棚 2..51 4 4 4 3
升高牛棚 2..51 5 5 5 4
降低牛棚 3..41 5 4 4 4
降低牛棚 3..41 5 3 3 4

 

解题思路

  首先把$A$序列变成$B$序列与把$B$序列变成$A$序列是等价的,只是把操作逆过来,因此把$A$序列变成$B$序列的最小操作次数与把$B$序列变成$A$序列的最小操作次数是一样的。

  我们先求这两个序列的差$C$,例如样例,我们得到两个序列的差就是$0~3~1~1~3$,表明我们要在序列$1~2~2~2~1$的基础上加上$0~3~1~1~3$才能变成$1~5~3~3~4$。因此问题变成了至少需要多少次操作使得全$0$的序列变成$0~3~1~1~3$,更一般的,需要最小多少次的操作使得全$0$的序列变成两个序列的差$C$。等价的,需要最小多少次的操作使得两个序列的差$C$变成全$0$的序列。

  我们来考虑$C$的差分数组,与上一题一样,我们要让$C$全部变成$0$,那么$C$对应的差分数组中的$diff \left[ 2 \right]$到$\left[ n \right]$应该都为$0$(因为原序列中每个数都相等,都等于$0$),且$diff\left[ 1 \right]$也等于$0$(因为当$diff \left[ 1 \right]$到$\left[ n \right]$都为$0$时,$diff\left[ 1 \right]$决定了原序列)。我们每次可以让序列$C$的某一段加上$1$或$-1$,对应的让差分序列的某两个数一个加$1$另外一个减$1$。

  一样的,差分序列的长度是$n+1$,我们要让$diff \left[ 1 \right]$到$diff \left[ n \right]$全部变成$0$(注意这题是$1 \sim n$),$diff \left[ {n+1} \right]$可以是任意值。与上题的贪心思路一样,假设差分数组中所有正数的和为$p$,所有负数的和的绝对值为$q$,假设$p$和$q$同时大于$0$,那么我们在$diff \left[ 1 \right]$到$diff \left[ n \right]$中,让一个正数数减$1$,一个负数加$1$,这样的操作共$min \{ {p, q} \}$次。然后剩下的只有正数或负数,我们让这些数与$diff \left[ {n+1} \right]$进行加$1$和减$1$,这样的操作共$\left| {p-q} \right|$次。因此最小的操作次数为$max \{ {p, q} \}$。

  AC代码如下:

 1 #include <cstdio>
 2 #include <algorithm>
 3 using namespace std;
 4 
 5 const int N = 1e5 + 10;
 6 
 7 int a[N];
 8 
 9 int main() {
10     int n;
11     scanf("%d", &n);
12     for (int i = 1; i <= n; i++) {
13         scanf("%d", a + i);
14     }
15     for (int i = 1; i <= n; i++) {
16         int val;
17         scanf("%d", &val);
18         a[i] -= val;
19     }
20     
21     for (int i = n; i; i--) {
22         a[i] -= a[i - 1];
23     }
24     
25     int p = 0, q = 0;
26     for (int i = 1; i <= n; i++) {
27         if (a[i] > 0) p += a[i];
28         else q -= a[i];
29     }
30     
31     printf("%d", max(p, q));
32     
33     return 0;
34 }

 

参考资料

  AcWing 100. 增减序列(算法提高课):https://www.acwing.com/video/763/

  AcWing 4262. 空调(春季每日一题2022):https://www.acwing.com/video/3868/

posted @ 2022-05-17 22:25  onlyblues  阅读(184)  评论(0编辑  收藏  举报
Web Analytics