题目描述:给定一个长度为n的数组给一个数k,你可以进行一下两种操作
1.数组中的一个数减1
2.用数组中的一个数赋值给数组中的另一个数
(操作1 2 等价)
要求操作次数最小,使数组的和小于等于k
input:n,k
a1,a2,,,an
output:
ans(最小操作数,)
example:
1 10
20
0
7 8
1 2 1 3 1 2 1
2
题目分析:这题我最开始想的是二分答案,即找到符合答案的最小值,但是check()函数,写的不对。看了大佬的代码才明白,是贪心+模拟,而且思想非常妙。
首先,数组应该sort排序,操作1减,操作2换,在操作数最小,而且数组sum减少的最多,那么应该是不断的拿最小的数a[1]去换最后面的大数,那么减1的操作的功能性就体现出来了,只给a[1]即最小的数减1,拿这个减1后的数去换后面的大数,这样sum应当是减少的最多的,且操作数也小,最后,做一个前缀和处理,不断的去模拟换和减并比较结果记录答案。(换数较好模拟,减1操作其实虚拟模拟,这也是大佬的代码的巧妙之处)。
代码简短,但隐含的意义的思想都很妙。
#include<iostream>
#include<string>
#include<memory.h>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<algorithm>
#include<functional>
#include<cmath>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N = 2e5 + 7;
const int inf = 0x3f3f3f3f;
ll s[N], pre[N];
ll n, k, sum;
void solve()
{
memset(pre, 0, sizeof(pre));
ll ans = 2e18;
cin >> n >> k;
for (int i = 1; i <= n; i++) cin >> s[i];
sort(s + 1, s + 1 + n);
for (int i = 1; i <= n; i++) pre[i] = pre[i - 1] + s[i];//前缀和处理
for (int i = 1; i <= n; i++)
{
ll now = pre[i] + (n - i) * s[1];//先假设拿a[1]换掉最后n-i个数可行
if (now <= k) ans = min(ans, ll(n - i));//如果可行,直接比较答案
else//换掉最后n-i数不可行,那么补救的方案就是使a[1]减1
{ //te表示应当再使a[1]减几次1才可行。now-k表示多余的要减掉的,这些多余要减的,
ll te = ceil((now - k) * 1.0 / (n - i + 1));//分给a[1]和后n-i个数,就等于每1个要减的数,
//此处即虚拟模拟减1操作 即a[1]要减的数。ceil向上取整
// 如多余的数为5,而且只拿a[1]换最后一个5/2=2
//但使a[1]减2,整体才减4,所以应当减3,整体减6
ans = min(ans, ll(n - i + te));
}
}
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int t = 1;
cin >> t;
while (t--)
solve();
return 0;
}
/*
*/