代码改变世界

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 <cstdio>
#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;
}