CF1622C Set or Decrease

题目地址

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=ai1

操作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=1naik.

  • 对于操作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=abai.
  • 设操作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 (a1x)(y+1)+Sum(2,ny)<=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 (a1x)(y+1)+Sum(2,ny)<=k
( a 1 − x ) ∗ ( y + 1 ) < = k − S u m ( 2 , n − y ) (a_1-x)*(y+1)<=k-Sum(2,n-y) (a1x)(y+1)<=kSum(2,ny)
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) a1y+a1xyx<=kSum(2,ny)

整理一下,最终得到:

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(kSum(2,ny)a1ya1)

通过枚举 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;
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值