Canada Cup 2016 E

题意:

Alfred想要买个S元S <= 20W的东西。。他现在有n枚硬币,每枚硬币的面值为[1,c] c <= 20W,他买东西的时候遵循一个贪心策略。。每次优先支付当前面值最大的硬币,但是这个面值不能超过还需支付的钱数

比如想买一个东西已经支付了x元,还要支付233元,剩余硬币中面值最大的是250元次大的是200元,这时会支付那个200元的硬币,以此类推。。

这家店不找零,Alfred也不想亏钱。。所以当且仅当他有办法刚好支付S元的时候他才会买那个东西

这家店不找零,这样的贪心策略显然是漏洞百出的,Bob想证明这个方法是错的,他的方法是,给Alfred一些硬币加入Alfred的集合里,然后让Alfred用新的硬币集合购物并且让Alfred没办法买这个东西

现在Bob想知道,他最少给Alfred多少钱能让Alfred没办法买这个东西,或者告诉Bob无论他给Alfred多少钱Alfred的算法总是正确的


solution:

首先明确,要让方案最优,每个给予Alfred的硬币都会被使用

如果有一个方案,使得Bob花费k元,用x个硬币,让Alfred买不了这个东西,那么一定有一个方案,使得Bob花费k*x元,用一个硬币,使得Alfred买不了这个东西

假设花费硬币的序列为c1,c2,c3,...,cm,就是第一次用了面值为c1,第二次为c2。。。

只要能证明k = 2时成立,那么k > 2时能够用k = 2逐步推出去

假设给了Alfred两枚硬币,形成新序列c1,c2,c3,...,cx,...,cy,...,cz

既然x,y两枚硬币都被Alfred选中,那么在选择cx之前的任意时刻,Alfred还需要支付的钱总是大于等于cx+cy的,,于是用cx+cy这枚硬币代替cx与cy,这枚硬币一定会被选中

既然Bob让Alfred买不到东西了,那么任意时刻,剩余需要支付的钱总是大于0的

也就是说S - ∑c > 0

原来的序列是c1,c2,c3,...,cx,...,cy,...cz

替换以后变成了c1,c2,c3,...,ct,...,cz,ct = cx + cy

ct之前的序列显然不变了,ct之后的序列,因为你花了ct元,后面剩余的钱就更少了些

那么后面每种状态取的硬币的面值和数量就不可能更多了,

但是每个时刻剩余的钱总是大于0,也就是说它能贴着原序列走,

或者说,原序列与新序列,除了将cx,cy替换成ct,其他位置的使用面值完全一样

因此,cx,cy两枚硬币,若能使Alfred无法购买物品,那么用一枚面值ct = cx+cy的硬币,同样达到目的


为什么要证明这个呢?

单枚硬币面值显然∈[1,20W],你总不能搞出一个花费比S还大的方案?!

既然多枚硬币的情况都能用一枚硬币等效,那么可以枚举给Alfred哪个硬币

从1到20W都试一遍,哪个可以答案就是哪个了(或者全部都不行)

但是如何快速判断可行性?

预处理好剩余x元时能使用的最大硬币的面值,记为f[x]

预处理好最大可能使用完面值为x的硬币后,剩余硬币的最大面值为nex[x]

假设我们当前剩余x元,使用面值为y的硬币支付,这样剩下res元

那么下一次使用的硬币面值k,一定满足k = min(f[res],nex[y]) (这个很显然啊。。不解释了)

暴力这样不断解决就行了?是啊。。

考虑最坏的情况,每种硬币的数量都很小,就是说你用完x元的硬币后还能用x-1元的硬币消

这种情况下假设一开始是用n元,最后用1元

那么你一共支付了n*(n-1)/2元,

换句话说,你用了根号种不同面值完成了这次判断,,

因此,总复杂度O(c根号c) 最坏考虑。。。但是cf的机子一点不怂~

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;

const int maxn = 2E5 + 20;
const int N = 200000;

int n,g,cnt,c[maxn],f[maxn],nex[maxn];

void Dec(int &x,int y,int z) {x -= min(z,x/y)*y;}
bool Judge(int now)
{
	int res = g,k = N+1; 
	bool flag = 0;
	while (res) {
		k = min(f[res],nex[k]);
		if (!k) return 1;
		if (k <= now && !flag) Dec(res,now,1),flag = 1;
		Dec(res,k,c[k]);
		if (!res) return 0;
	}
}

int main()
{
	#ifdef DMC
		freopen("DMC.txt","r",stdin);
	#endif
	
	cin >> g >> n;
	for (int i = 1; i <= n; i++) {int x; scanf("%d",&x); ++c[x];}
	int last = 0;
	for (int i = 1; i <= N; i++) {
		if (c[i]) last = i; f[i] = last;
		nex[i] = c[i-1]?i-1:nex[i-1];
	}
	nex[N+1] = N;
	for (int i = 1; i <= N; i++)
		if (Judge(i)) {cout << i; return 0;}
	puts("Greed is good");
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值