Nju第五届ACM程序设计竞赛 F题 Is Greed Correct
2007-05-23 12:49 老博客哈 阅读(2027) 评论(2) 编辑 收藏 举报Nju第五届ACM程序设计竞赛 F题
Is Greed Correct
Time limit : 2 seconds
Memory limit: 64 megabytes
换零钱问题的典型问法为:
给定一系列的硬币面值和一个需要找的钱数,要求把这个钱数换成硬币且使用的个数最少。
对于这个问题,贪心法可以找到一个不错的解,但不一定是最优解。贪心法是这样的:假设要找的
钱数是A,每次找出不大于A的最大的面值的硬币,从A里面减去,至到A=0.我们假设我们所考虑的
硬币系统里面总有一个面值为1,那么问题一定有解。
我们的问题是给定一个硬币系统,判断用贪心法来换零钱是否总能得到最优解,即判断是否对任意的A,贪心法得到的硬币组合方案是否总是最少的。
Input
输入的第一行是一个整数T,表示测试数据的组数。下面有T组测试数据。
每组测试数据有两行: 第一行是一个整数M(2 < M <= 100);第二行是M个用空格隔开的整数,表示这个硬币系统中有哪些币值。每个数都不大于10^7,M个整数互不相同,且一定有一个是1.
Output
对于每组测试数据如果贪心法对于这个硬币系统总是最优的,输出"Yes",否则输出"No"(引号不用输出)。
Example
Input Output
2 Yes
6 No
1 5 10 25 50 100
3
1 3 4
Hint:
第一组数据是贪心最优的,因为我们生活中使用的是硬币系统是贪心最优的。第二组不是,这是一个反例:如果需要组成6,使用贪心算法的组合为6=4+1+1,需要3个硬币,而最优的组合为6=3+3,仅需两个硬币。
这个题目很有意思,不过刚开始我没想法。后来ufx告诉我是论文题,我小小的郁闷了一下。为什么说这个问题好呢,因为前不久我在hdoj上刚做了一题“发工资咯:)”,地址在:http://acm.hdu.edu.cn/showproblem.php?pid=2021 这个题目里面的硬币系统就是所谓的canonical,也就是用贪心算法去算得到的硬币个数最少,当时我的队友sigh用bfs去做的,可惜tle了。而现在的这个题目不是纯粹的要我们去求最少的硬币个数,而是要我们判断该硬币系统是不是最优的。看了下标程,panda是判断的范围是1~2*C0(假设硬币C已经按从大到小进行排序过了),然后使用贪心策略保存范围内每种钱的最少个数。这个方法我不能完全肯定它的正确性,ufx也没谈到。
直到看完A Polynomial-time Algorithm for the Change-Making Problem这篇论文之后,才霍然开郎,作者深入剖析了这个问题,论文中假设了一个最小反例,并断言该反例必然存在与一个(n^2)大小的集合内,这里的n指的是硬币系统的个数。然后再利用O(n)的算法检测这些反例是否使得在贪心策略下的个数最少。 其中最重要的一条定理我认为是 最小反例w的最优表示与C(i - 1)的贪心表示前j-1位相同,且第j位要大一,后面全0。
知道了这个结论,就不难写出算法了,下面把官方的标程贴一下:
#include <algorithm>
int g[128], g1[128];
int c[128];
int M, m;
int solve(int x, int *a)
{
int ans = 0;
for(int i = 0; i < m; i++) {
a[i] = x / c[i];
x %= c[i];
ans += a[i];
}
return ans;
}
bool compare(const int &a, const int &b)
{
return a > b;
}
int main()
{
int t, good;
scanf("%d", &t);
while(t--) {
scanf("%d", &m);
for(int i = 0; i < m; i++)
scanf("%d", &c[i]);
std::sort(c, c + m, compare);
good = 1;
for(int i = 1; i < m; i++) {
solve(c[i - 1] - 1, g);
for(int j = i; j < m; j++) {
int w = 0, v = 0;
for(int k = 0; k < j; k++) {
w += g[k] * c[k];
v += g[k];
}
w += (g[j] + 1) * c[j];
v += g[j] + 1;
if(solve(w, g1) > v) {
// printf("%d %d\n", w, v);
good = 0;
break;
}
}
if(!good)
break;
}
if(good)
printf("Yes\n");
else
printf("No\n");
}
return 0;
}