差分的应用
前言
之前一直不知道差分数组的本质含义,只知道差分数组是用来处理这样一种情况的,就是当需要给原序列$\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..5:1 3 3 3 2 升高牛棚 2..5:1 4 4 4 3 升高牛棚 2..5:1 5 5 5 4 降低牛棚 3..4:1 5 4 4 4 降低牛棚 3..4:1 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/
本文来自博客园,作者:onlyblues,转载请注明原文链接:https://www.cnblogs.com/onlyblues/p/16282454.html