题目描述
难度分:1500
输入T(≤2×105)表示T组数据。所有数据的n之和≤2×105。
每组数据输入n(1≤n≤2×105)和长为n的数组a(1≤a[i]≤109)。
每次操作,你可以选择一个大于1的a[i],删除它,然后在它的位置上插入x和a[i]−x,其中x是一个小于a[i] 的正整数。
例如a=[1,10,9],把10分成3和7,数组变成[1,3,7,9]。
你需要把a变成非递减的,即a[i]≤a[i+1]。
输出最少操作次数。
输入样例
4
3
1 3 2
4
1 2 3 4
3
3 2 1
7
1 4 4 3 5 7 6
输出样例
1
0
3
9
算法
贪心
因为并不确定第一个元素要不要拆,但是可以确定最后一个元素肯定是不拆更好,否则会增加前面元素的拆分操作,所以从后往前贪心。记上一个元素为last(初始情况下last=a[n]),当遍历到a[i]时如果发现a[i]>last,说明a[i]是必须要拆分的。为了让前面的数拆分次数尽可能少,最好的方式肯定是让a[i]的拆解更加平均,并且最大的值应该尽可能接近last,这样就能保证a[i]被拆解后的值尽量大,那么前面的数就有更大的可能性不拆。
a[i]被拆分之后的值必须在区间[1,last]之中,因此可以得到拆分数的个数t=[a[i]+last−1last],其中[.]为对.的下取整操作(此时可以让[a[i]t]最接近last),拆分操作的次数为t−1,累加到答案上。拆分出来的最小数应该为[a[i]t],让它更新last,继续考前前一个数需不需要拆分即可。
复杂度分析
时间复杂度
算法整体就是倒序遍历了一遍数组,每次计算都是O(1)的,因此时间复杂度为O(n)。
空间复杂度
整个算法过程只使用了有限几个变量,空间复杂度为O(1)。
C++ 代码
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 200010;
int t, n, a[N];
void solve() {
int last = a[n];
long long ans = 0;
for(int i = n - 1; i >= 1; i--) {
if(a[i] <= last) {
last = a[i];
}else {
int t = (a[i] + last - 1) / last;
last = a[i] / t;
ans += t - 1;
}
}
printf("%lld\n", ans);
}
int main() {
scanf("%d", &t);
while(t--) {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
solve();
}
return 0;
}