题目链接:
https://codeforces.com/problemset/problem/1622/C
题目大意:
给定一个序列 \(a_1\),\(a_2\),\(a_3\) ... \(a_n\),和一个 \(k\),一步操作中你可以选择一个 \(a_i\) 减去 1,或者让 \(a_i\) = \(a_j\),求最少多少步使 \(\sum_{i = 1}^n\)\(a_i\) <= k。
思路:
可以发现,整个操作的过程和序列的顺序没有关系,于是进行一个排序,使序列中的值从小到大。
容易想到 最优方案 一定是先进行 减 的操作再进行 赋值 的操作,即对最小的数进行一些减的操作,然后再通过最小数对其他数进行赋值赋值操作。
定义 \(i\) 为对 \(i\) 个数进行赋值操作,当赋值后值 不满足 \(\sum_{i = 1}^n\)\(a_i\) <= k,要在赋值前进行 减 的操作,记 当前得到的总和为 \(p\),当前仍多出 \(p - k\),由于后续进行的是赋值的操作,那么 \(a_1\) 的变化也是后面 \(i\) 个数会发生的变化,那么 \(a_1\) 需要减去 \(\lceil(p - k) / (i + 1)\rceil\),即再进行 \(\lceil(p - k) / (i + 1)\rceil\) 次操作,而这个等式等价于 (p - k + i) / (i + 1),因为当 (p - k) % (i + 1) 有余数的时候,加上 \(i\),就实现了向上取整。
代码:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
LL T, n, k;
void solve(){
cin >> n >> k;
vector <LL> a(n + 1);
for (int i = 1; i <= n; i++)
cin >> a[i];
sort(a.begin(), a.end());
vector <LL> sum(n + 1);
sum[0] = 0;
for (int i = 1; i <= n; i++)
sum[i] = sum[i - 1] + a[i];
LL ans = 1e18;
for (LL i = 0; i < n; i++){
LL p = sum[n - i] + a[1] * i;
if(p > k) ans = min(ans, i + (p - k + i) / (i + 1));
else ans = min(ans, i);
}
cout << ans << "\n";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cin >> T;
while(T--)
solve();
return 0;
}