题目链接:

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;
}
posted on 2022-01-03 10:26  Hamine  阅读(1701)  评论(0编辑  收藏  举报