差分的应用
前言
之前一直不知道差分数组的本质含义,只知道差分数组是用来处理这样一种情况的,就是当需要给原序列这个区间范围内加上一个数是,并不需要从循环到来给每个数加上,只需要对原序列对应的差分数组进行就可以了,之后再对差分数组求前缀和就可以得到修改后的序列。
事实上差分数组本身的含义是两个相邻数值的差值,比如有原序列和对应的差分序列,那么就表示与相差的大小,即。一个序列的差分序列就是这样子构造出来的,当,;当,,即:
可以发现,,即对差分数值求前缀和就会得到原序列。
因为没意识到这点,所以下面的题完全没有思路。
增减序列
给定一个长度为 的数列 ,每次可以选择一个区间 ,使下标在这个区间内的数都加一或者都减一。
求至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列可能有多少种。
输入格式
第一行输入正整数 。
接下来 行,每行输入一个整数,第 行的整数代表 。
输出格式
第一行输出最少操作次数。
第二行输出最终能得到多少种结果。
数据范围
,
输入样例:
4 1 1 2 2
输出样例:
1 2
解题思路
从差分的角度来思考这个问题,我们要把原序列中的所有数变得一样,意味着对应的差分数组在到应该都为,即,因为按照差分数组的定义,如果差分数组为,意味着两个相邻的数的差应该为,即这两个数相等。比如,说明和的差为,即,以此类推,一直到。可以是任意值,并且最后整个序列是由决定的(整个序列中的数都是)。
因此整个问题变成了,最少操作多少次,使得到全部变成,以及在最小操作的前提下,有多少种不同的值。
我们原本是要对原序列的到都加上或,现在变成了只需对和。
并且由于序列的下标是,最大取到,因此差分数组有项,从到。我们的目的是让到全部变成,其中和可以是任意的值。下面我们来看一下不同的操作对差分数组的影响。
以下和表示对原序列到都加上或。
- 当
表示令,或。即在到中令一个数加,另外一个数。
- 当
表示令,到中的某个数;或,到中的某个数。即让到中的某个数。
- 当
表示令,到中的某个数;或,到中的某个数。即让到中的某个数。
- 当
表示对原序列的每个数都加上同一个数,无意义。
因此最少用前种的操作多少次,使得到全部变成?我们会贪心地想,如果差分数组中既存在正数和负数,那么我们应该用第一种操作同时让正数和负数都减少个,直到差分数组中只存在正数或负数,这时就选第个操作或第个操作。
假设差分数组中所有正数的和为,所有负数的和的绝对值为,按照上面的贪心做法,那么最小的操作次数为。
我们先求理论最小值是多少,假设有,因为我们每次的操作最多让减少个,因此要让变为,至少要次操作,即最少次数应该,又因为我们上面构造了一种方法使得操作的次数恰好为,因此这种贪心的做法是正确的。如果,同理。
最后一个问题是可以得到多少种不同的序列。前面已经分析过整个序列是由决定的,因此我们应该分析在最小操作次数的前提下,会被改变多少次。又因为当和同时大于时都会用第种操作,且第种操作不会对产生影响,因此我们应该看当和有一个为情况。此时还需要进行的次操作或操作,且只有操作会改变。我们可以选择的操作的次数有次(当然,剩下的操作次数应该分配给操作),因此有种取值,即可以得到的不同的序列有种。
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 的 头奶牛对他们牛棚的室温非常挑剔。
有些奶牛喜欢温度低一些,而有些奶牛则喜欢温度高一些。
Farmer John 的牛棚包含一排 个牛栏,编号为 ,每个牛栏里有一头牛。
第 头奶牛希望她的牛栏中的温度是 ,而现在她的牛栏中的温度是 。
为了确保每头奶牛都感到舒适,Farmer John 安装了一个新的空调系统。
该系统进行控制的方式非常有趣,他可以向系统发送命令,告诉它将一组连续的牛栏内的温度升高或降低 个单位——例如「将牛栏 的温度升高 个单位」。
一组连续的牛栏最短可以仅包含一个牛栏。
请帮助 Farmer John 求出他需要向新的空调系统发送的命令的最小数量,使得每头奶牛的牛栏都处于其中的奶牛的理想温度。
输入格式
输入的第一行包含 。
下一行包含 个非负整数 ,用空格分隔。
最后一行包含 个非负整数 。
输出格式
输出一个整数,为 Farmer John 需要使用的最小指令数量。
数据范围
,
输入样例:
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
解题思路
首先把序列变成序列与把序列变成序列是等价的,只是把操作逆过来,因此把序列变成序列的最小操作次数与把序列变成序列的最小操作次数是一样的。
我们先求这两个序列的差,例如样例,我们得到两个序列的差就是,表明我们要在序列的基础上加上才能变成。因此问题变成了至少需要多少次操作使得全的序列变成,更一般的,需要最小多少次的操作使得全的序列变成两个序列的差。等价的,需要最小多少次的操作使得两个序列的差变成全的序列。
我们来考虑的差分数组,与上一题一样,我们要让全部变成,那么对应的差分数组中的到应该都为(因为原序列中每个数都相等,都等于),且也等于(因为当到都为时,决定了原序列)。我们每次可以让序列的某一段加上或,对应的让差分序列的某两个数一个加另外一个减。
一样的,差分序列的长度是,我们要让到全部变成(注意这题是),可以是任意值。与上题的贪心思路一样,假设差分数组中所有正数的和为,所有负数的和的绝对值为,假设和同时大于,那么我们在到中,让一个正数数减,一个负数加,这样的操作共次。然后剩下的只有正数或负数,我们让这些数与进行加和减,这样的操作共次。因此最小的操作次数为。
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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效