bzoj1816 扑克牌 贪心(不用二分QvQ)

我们发现,拥有的牌的种类越多,对我们越有利。

所以想一想暴力策略,就是选出所有牌堆中最少的那一堆,用Jocker代替,否则取一张,不断进行本操作。

而你发现这样取着取着,就会有很多牌堆里的牌张数一样了,并且一定是初始最少的牌的张数会变得一样。

假设前 i i i堆已经变得张数一样了,那么我们考虑在前 i i i堆每一堆上加一张Jocker,然后取 i i i次将加进去的Jocker取完,这叫做一组操作。每做完一组操作,前 i i i堆各少 i − 1 i-1 i1张牌,后 n − i n-i ni堆每堆少 i i i张牌,所以做 c i + 1 − c i c_{i+1}-c_i ci+1ci组后,前 i + 1 i+1 i+1堆的牌张数就一样了。

于是我们这么贪心的取即可,当然取的过程中,可能因为两个限制无法正好做 c i + 1 − c i c_{i+1}-c_i ci+1ci组操作,一个是Jocker的数量,一个是前 i i i堆每堆的牌的数量。

做完这些一组一组的操作后,再做每一堆取一张。所以你发现,假设最终受到限制导致只能做 k k k组,做完 k k k组后还剩一些Jocker,如果继续使用它们,并不能使答案更优,所以剩的那些Jocker也不用使用了。当然如果一直做到所有牌堆的牌数都相同了还有剩的Jocker,当然要用掉。

复杂度 O ( n log ⁡ n ) O(n \log n) O(nlogn),时间瓶颈在排序。

#include<bits/stdc++.h>
using namespace std;
#define RI register int
const int inf=0x3f3f3f3f;
int n,m,ans,orz,c[55],s[55];//s:差分记录牌的减少情况
int main()
{
	scanf("%d%d",&n,&m);
	for(RI i=1;i<=n;++i) scanf("%d",&c[i]);
	sort(c+1,c+1+n);
	for(RI i=1;i<n;++i) {
		int d=c[i+1]-c[i];
		if(d*i>m-ans) {
			int k=(m-ans)/i;
			if(d*(i-1)>c[i]-ans) k=min(k,(c[i]-ans)/(i-1));
			ans+=k*i,s[1]-=k*(i-1),s[i+1]+=k*(i-1),s[i+1]-=k*i;break;
		}
		else if(d*(i-1)>c[i]-ans) {
			int k=(c[i]-ans)/(i-1);
			ans+=k*i,s[1]-=k*(i-1),s[i+1]+=k*(i-1),s[i+1]-=k*i;break;
		}
		s[1]-=d*(i-1),s[i+1]+=d*(i-1),s[i+1]-=d*i;
		ans+=d*i;
	}
	orz=inf;
	for(RI i=1;i<=n;++i) s[i]+=s[i-1],c[i]+=s[i],orz=min(orz,c[i]);
	int k=min((m-ans)/n,orz/(n-1));
	ans+=k*n,orz-=k*(n-1);
	printf("%d\n",ans+orz);
	return 0;
}
  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值