春春幼儿园举办了一年一度的“积木大赛”。
今年比赛的内容是搭建一座宽度为n的大厦,大厦可以看成由n块宽度为1的积木组成,第i块积木的最终高度需要是hiℎi。
在搭建开始之前,没有任何积木(可以看成块高度为 0 的积木)。
接下来每次操作,小朋友们可以选择一段连续区间[L,R],然后将第L块到第R块之间(含第 L 块和第 R 块)所有积木的高度分别增加1。
小M是个聪明的小朋友,她很快想出了建造大厦的最佳策略,使得建造所需的操作次数最少。
但她不是一个勤于动手的孩子,所以想请你帮忙实现这个策略,并求出最少的操作次数。
输入格式
输入包含两行,第一行包含一个整数n,表示大厦的宽度。
第二行包含n个整数,第i个整数为hiℎi。
输出格式
仅一行,即建造所需的最少操作数。
数据范围
1≤n≤1051≤n≤105,
0≤hi≤100000≤hi≤10000
输入样例:
5
2 3 4 1 2
输出样例:
5
算法
(差分,贪心) O(n)
我们逆向思考:假设给定了每块积木的高度,每次可以将某一段区间中的所有高度减一,问最少操作多少次可以将所有高度变成0。
原序列是: h1,h2,h3,…,hn 其中 hi≥1。
构造差分序列:
b1=h1
b2=h2−h1
b3=h3−h2
…
bn=hn−hn−1
bn+1=−hn
则将 hL,hL+1,…,hRhL,hL+1,…,hR 中的每个数减1的操作,等价于将 bL 减1, 将 bR+1 加1。关于差分算法可以参考 AcWing 797.。
因此问题变成每次从 b1,b2,…,bn+1中挑两个数,前一个减1,后一个加1,问最少操作多少次可以将所有数变成0。
首先,对于每个正数 bi>0,最少需要操作 bi 次,因此总操作次数一定不少于差分数组中所有正数之和 B。
然后我们构造一种操作方式,使得恰好可以通过 B 次操作,将所有数变成0。 那么就可以说明最少操作次数一定是所有正数之和。
操作方式如下:
从后往前操作,如果当前的 bi>0,则将其减1,并将其后的某个负数加1。
由于 ∑bi=0,因此从数量上看,正数和负数是一一对应的。
然后我们考虑在操作过程中,对于每个正数 bi,其后是否一定存在负数与之对应。由于差分数组的所有后缀 ∑n+1i=kbi=−hk≤0,因此在所有后缀中,正数之和一定小于等于负数之和,所以负数总是可以被找到的。
因此,上面的操作方式是合法的。
时间复杂度
求差分序列扫描一遍数组即可,因此总时间复杂度是 O(n)O(n)。
作者:yxc
链接:https://www.acwing.com/solution/content/3256/
来源:AcWing
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
/* 因此问题变成每次从差分数组: b1,b2,…,bn+1b1,b2,…,bn+1 中挑两个数, 前一个减1,后一个加1,问最少操作多少次可以将所有数变成0。 首先,对于每个正数 bi>0,最少需要操作 bi 次,因此总操作次数一定不少于 差分数组中所有正数之和 B。然后我们构造一种操作方式,使得恰好可以通过 B 次操作, 将所有数变成0。 那么就可以说明最少操作次数一定是所有正数之和。 由于差分数组的所有后缀 ∑i=kn+1bi=-hk≤0,因此在所有后缀中, 正数之和一定小于等于负数之和,所以负数总是可以被找到的。 因此,上面的操作方式是合法的。 求差分序列扫描一遍数组即可,因此总时间复杂度是 O(n)。 */ #include<iostream> using namespace std; int n,ans=0; int a[100100]; int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++) //差分数组的灵活应用 if(a[i]-a[i-1]>0) ans+=a[i]-a[i-1]; cout<<ans<<endl; return 0; }