题目地址
https://codeforces.com/problemset/problem/1622/C
题目描述
略
解题思路
对于某个数 a i a_i ai有两个操作:
操作1: a i a_i ai自减使得 a i = a i − 1 a_i=a_i-1 ai=ai−1
操作2:赋值 a i = a j a_i=a_j ai=aj
求最少操作的次数,使得 ∑ i = 1 n a i ≤ k \displaystyle\sum_{i=1}^n a_i≤k i=1∑nai≤k.
- 对于操作2,肯定是最小的赋值赋给较大值操作的次数会是最少
- 对于操作1,对最小的值自减,这样赋值给其它较大值时操作的次数也是最少的,更快地接近 k k k.
利用上面的贪心思路,对数组 a a a从小到大排序,对 a 1 a_1 a1做操作1,从 a n a_n an开始做操作2.
如何求出操作1和操作2的次数?
- 设数组区间和 S u m ( a , b ) = ∑ i = a b a i Sum(a,b)= \displaystyle\sum_{i=a}^b a_i Sum(a,b)=i=a∑bai.
- 设操作1的次数为 x x x.
- 操作2的次数为 y y y .
于是有了下面的表达式:
(
a
1
−
x
)
∗
(
y
+
1
)
+
S
u
m
(
2
,
n
−
y
)
<
=
k
(a_1-x)*(y+1)+Sum(2,n-y)<=k
(a1−x)∗(y+1)+Sum(2,n−y)<=k
比较容易的方法当然是枚举所有的 x x x和 y y y,但根据题中给的数据范围,必然会超时。所以只能枚举其中的一个变量,根据数据范围,枚举 y y y是比较明智的,把式子简单转化一下,有:
(
a
1
−
x
)
∗
(
y
+
1
)
+
S
u
m
(
2
,
n
−
y
)
<
=
k
(a_1-x)*(y+1)+Sum(2,n-y)<=k
(a1−x)∗(y+1)+Sum(2,n−y)<=k
(
a
1
−
x
)
∗
(
y
+
1
)
<
=
k
−
S
u
m
(
2
,
n
−
y
)
(a_1-x)*(y+1)<=k-Sum(2,n-y)
(a1−x)∗(y+1)<=k−Sum(2,n−y)
a
1
∗
y
+
a
1
−
x
∗
y
−
x
<
=
k
−
S
u
m
(
2
,
n
−
y
)
a_1*y+a_1-x*y-x<=k-Sum(2,n-y)
a1∗y+a1−x∗y−x<=k−Sum(2,n−y)
整理一下,最终得到:
x > = − ( k − S u m ( 2 , n − y ) − a 1 ∗ y − a 1 ) y + 1 x >= \frac {-(k-Sum(2,n-y)-a_1*y-a_1)}{y+1} x>=y+1−(k−Sum(2,n−y)−a1∗y−a1)
通过枚举 y y y,直接求出 x x x即可.
数据类型卡了好几次,反正无脑上long long
就是了。
参考代码
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int MAXN=200005;
long long a[MAXN];
long long s[MAXN];
int main(){
long long t,n,k,ans;
cin>>t;
while(t--){
cin>>n>>k;
ans= 0x7fffffff;
for(int i=1;i<=n;i++)
cin>>a[i];
sort(a+1,a+1+n);
for(int i=1;i<=n;i++){
s[i]=a[i];s[i]+=s[i-1];
}
if(s[n]<=k) {
cout<<0<<endl;
continue;
}
for(int y=0;y<n;y++)
{
long long temp=k-s[n-y]+s[1]-a[1]-a[1]*y;
long long x=ceil(-1.0*(long long)(temp)/(y+1));
//printf("x:%d,y:%d\n",x,y);
//printf("res:%d\n",(a[1]-x)*(y+1)+s[n-y]-s[1]);
//x为负数时表示不需要减任何数也能满足条件
if(x<=0) x=0;
if(x+y<ans) ans=x+y;
}
cout<<ans<<endl;
}
return 0;
}