题目描述
难度分:1800
输入T(≤104)表示T组数据。所有数据的n之和≤2×105。
每组数据输入n(1≤n≤2×105)和长为n的数组c(1≤c[i]≤109)。
然后输入k(1≤k≤109)。
你有k块钱和一个长为n的全0数组a。每次操作,你可以花费c[i],把前缀a[1],…,a[i]都加一,前提是c[i]≤k。输出你能得到的字典序最大的a。
输入样例
4
3
1 2 3
5
2
3 4
7
3
3 2 1
2
6
10 6 4 6 3 4
7
输出样例
5 0 0
2 1
2 2 2
2 2 2 2 2 1
算法
反悔贪心
感觉这种贪心题真的好难,反悔贪心WA
了好久都搞不清错在哪了,打了这里的补丁又错了那里。
首先比较容易发现的一点是如果a[i]≥a[i+1],那a[i]就肯定没有必要选,因为选择后面的i+1可以让更长的前缀加1,还花费更小。所以先原地预处理一个c的后缀最小值数组,此时c就是单调不减的了。
然后正序遍历i∈[1,n]开始贪心:
-
如果i=1,令a[1]=⌊kc[1]⌋(⌊.⌋表示对.向下取整),这时候k减少a[1]×c[1]。
-
对于i>1的情况,我们开始反悔,因为存在这样的情况,在前面j位置可以加t次,但是把前面的花费剩下来,选择当前位置的c[i]也可以加t次,这样不仅不会让前面的a[j]减小,反而让后面多加了几个数,字典序更大。目的是让a[j]不变的前提下k+cnt×c[j]=cnt×c[i],a[i]最大,所以能变就变,cnt=a[i]=⌊kc[i]−c[j]⌋。分母有可能为0,此时要把i上的操作转移到i+1,直接令a[i+1]=a[i]。除此之外,i−1位置可能也不够⌊kc[i]−c[j]⌋次让你返悔,所以a[i]=min(a[i−1],⌊kc[i]−c[j]⌋)。
复杂度分析
时间复杂度
预处理出c的后缀最小值数组时间复杂度为O(n),之后遍历i∈[1,n]求答案时间复杂度还是O(n)。因此,整个算法的时间复杂度为O(n)。
空间复杂度
除了输入的数组c,还开辟了答案数组a的空间,额外空间复杂度为O(n)。
C++ 代码
#include <bits/stdc++.h>
using namespace std;
const int N = 200010;
int t, n, k, c[N], a[N];
int main() {
scanf("%d", &t);
for(int cases = 1; cases <= t; cases++) {
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &c[i]);
}
for(int i = n - 1; i >= 1; i--) {
c[i] = min(c[i], c[i + 1]);
}
scanf("%d", &k);
for(int i = 1; i <= n; i++) {
if(c[i] == c[i - 1]) {
a[i] = a[i - 1];
}else {
a[i] = min(k / (c[i] - c[i - 1]), i != 1? a[i - 1] : 0x3f3f3f3f);
k -= (c[i] - c[i - 1]) * a[i];
}
printf("%d%c", a[i], " \n"[i == n]);
}
}
return 0;
}