差分的应用

前言

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

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

{diff[1]=a[1]diff[2]=a[2]a[1]diff[3]=a[3]a[2]              diff[n]=a[n]a[n1]

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

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

 

增减序列

给定一个长度为 n 的数列 a1,a2,,an,每次可以选择一个区间 [l,r],使下标在这个区间内的数都加一或者都减一。

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

输入格式

第一行输入正整数 n

接下来 n 行,每行输入一个整数,第 i+1 行的整数代表 ai

输出格式

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

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

数据范围

0<n105,
0ai<2147483648

输入样例:

4
1
1
2
2

输出样例:

1
2

 

解题思路

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

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

  我们原本是要对原序列的a[l]a[r]都加上11,现在变成了只需对diff[l]±1diff[r+1]1

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

  以下lr表示对原序列a[l]a[r]都加上11

  • 2l,rn1

  表示令diff[l]+=1, diff[r+1]=1,或diff[l]=1, diff[r+1]+=1。即在diff[2][n]中令一个数加1,另外一个数1

  • l=1, rn1

  表示令diff[1]+=1diff[2]diff[n]中的某个数1;或diff[1]=1diff[2]diff[n]中的某个数+1。即让diff[2]diff[n]中的某个数±1

  • l2, r=n

  表示令diff[n+1]+=1diff[2]diff[n]中的某个数1;或diff[n+1]=1diff[2]diff[n]中的某个数+1。即让diff[2]diff[n]中的某个数±1

  • l=1, r=n

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

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

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

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

  最后一个问题是可以得到多少种不同的序列。前面已经分析过整个序列是由diff[1]决定的,因此我们应该分析在最小操作次数的前提下,diff[1]会被改变多少次。又因为当pq同时大于0时都会用第1种操作,且第1种操作不会对diff[1]产生影响,因此我们应该看当pq有一个为0情况。此时还需要进行的|pq|次操作2或操作3,且只有操作2会改变diff[1]。我们可以选择的操作2的次数有0,1,2,,|pq|次(当然,剩下的操作次数应该分配给操作3),因此diff[1]|pq|+1种取值,即可以得到的不同的序列有|pq|+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 个牛栏,编号为 1N,每个牛栏里有一头牛。

i 头奶牛希望她的牛栏中的温度是 pi,而现在她的牛栏中的温度是 ti

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

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

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

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

输入格式

输入的第一行包含 N

下一行包含 N 个非负整数 p1pN,用空格分隔。

最后一行包含 N 个非负整数 t1tN

输出格式

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

数据范围

1N105,
0pi,ti10000

输入样例:

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[2][n]应该都为0(因为原序列中每个数都相等,都等于0),且diff[1]也等于0(因为当diff[1][n]都为0时,diff[1]决定了原序列)。我们每次可以让序列C的某一段加上11,对应的让差分序列的某两个数一个加1另外一个减1

  一样的,差分序列的长度是n+1,我们要让diff[1]diff[n]全部变成0(注意这题是1n),diff[n+1]可以是任意值。与上题的贪心思路一样,假设差分数组中所有正数的和为p,所有负数的和的绝对值为q,假设pq同时大于0,那么我们在diff[1]diff[n]中,让一个正数数减1,一个负数加1,这样的操作共min{p,q}次。然后剩下的只有正数或负数,我们让这些数与diff[n+1]进行加1和减1,这样的操作共|pq|次。因此最小的操作次数为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 @   onlyblues  阅读(216)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
Web Analytics
点击右上角即可分享
微信分享提示