Nju第五届ACM程序设计竞赛 F题 Is Greed Correct

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。

    知道了这个结论,就不难写出算法了,下面把官方的标程贴一下:

None.gif #include  < cstdio >
None.gif#include 
< algorithm >
None.gif
None.gif
int  g[ 128 ], g1[ 128 ];
None.gif
int  c[ 128 ];
None.gif
int  M, m;
None.gif
None.gif
int  solve( int  x,  int   * a)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
int ans = 0;
ExpandedSubBlockStart.gifContractedSubBlock.gif    
for(int i = 0; i < m; i++dot.gif{
InBlock.gif        a[i] 
= x / c[i];
InBlock.gif        x 
%= c[i];
InBlock.gif        ans 
+= a[i];
ExpandedSubBlockEnd.gif    }

InBlock.gif    
return ans;
ExpandedBlockEnd.gif}

None.gif
None.gif
bool  compare( const   int   & a,  const   int   & b)
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
return a > b;
ExpandedBlockEnd.gif}

None.gif
None.gif
int  main()
ExpandedBlockStart.gifContractedBlock.gif
dot.gif {
InBlock.gif    
int t, good;
InBlock.gif    scanf(
"%d"&t);
ExpandedSubBlockStart.gifContractedSubBlock.gif    
while(t--dot.gif{
InBlock.gif        scanf(
"%d"&m);
InBlock.gif        
for(int i = 0; i < m; i++)
InBlock.gif            scanf(
"%d"&c[i]);
InBlock.gif        std::sort(c, c 
+ m, compare);
InBlock.gif        good 
= 1;
ExpandedSubBlockStart.gifContractedSubBlock.gif        
for(int i = 1; i < m; i++dot.gif{
InBlock.gif            solve(c[i 
- 1- 1, g);
ExpandedSubBlockStart.gifContractedSubBlock.gif            
for(int j = i; j < m; j++dot.gif{
InBlock.gif                
int w = 0, v = 0;
ExpandedSubBlockStart.gifContractedSubBlock.gif                
for(int k = 0; k < j; k++dot.gif{
InBlock.gif                    w 
+= g[k] * c[k];
InBlock.gif                    v 
+= g[k];
ExpandedSubBlockEnd.gif                }

InBlock.gif                w 
+= (g[j] + 1* c[j];
InBlock.gif                v 
+= g[j] + 1;
ExpandedSubBlockStart.gifContractedSubBlock.gif                
if(solve(w, g1) > v) dot.gif{
InBlock.gif                
//    printf("%d %d\n", w, v);
InBlock.gif
                    good = 0;
InBlock.gif                    
break;
ExpandedSubBlockEnd.gif                }

ExpandedSubBlockEnd.gif            }

InBlock.gif            
if(!good)
InBlock.gif                
break;
ExpandedSubBlockEnd.gif        }

InBlock.gif        
if(good)
InBlock.gif            printf(
"Yes\n");
InBlock.gif        
else
InBlock.gif            printf(
"No\n");
ExpandedSubBlockEnd.gif    }

InBlock.gif    
return 0;
ExpandedBlockEnd.gif}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值