给出不同类型的一组邮票,类型面值可能相同。求符合总面值,且最多取4张邮票的最佳方案。最佳方案满足以下要求:
搞了整整3天,绞尽脑汁也没想出个好方案。看了一下关于该题的discussion,许多人说这是个水题,直接暴搜就可以过。可是怎么都想不明白如何暴搜。 最后在网上看到一个利用动态规划法写的一个状态转移方程,顿时豁然开朗,想出了这个方案。关键在于如何递归计算最大类型数,以及判断最优解。
一. 计算最大类型数
用MT[k][n][v] (k>=0, 0<=n<=4, v>=0) 表示从前k个类型中,最多取n张,总面值为v的所有组合中最大的类型数。则有递推方程:
MT[k][n][v] = MAX{ MT[k-1][n-i][v-i*Values[k-1]] + (i>0?1:0) } (0<=i<=n, v>=i*Values[k-1])
二. 判断最优解,平局,及无解情况
<1>每次进行递归之前,先记录当前类型取用的个数
<2>每次递归到v=0时,即得到一个解,与之前的最优解比较,决定是否更新或平局。
<3>每次递归到n=0或v=0时, 如果v>0, 则此次递归无解。
基本代码也很简洁:
void searchBestComb(int k, int n, int v) { if(v>0 && n>0 && k>0) for(int i=0; i<=n && v>=i*typeValue[k-1]; ++i) { typeTimes[k-1] = i; //use i stamps of this type searchBestComb(k-1, n-i, v-i*typeValue[k-1]); } else if(v==0) updateBestComb(k); //find an answer }
提交后一次AC。看了一下ACM的解题报告,终于知道如何暴搜了,虽然效率不高,但也可以过。代码如下:
For(i=1;i<=total_stamps;i++) For(j=i;j<=total_stamps;j++) For(k=j;k<=total_stamps;k++) For(l=k;l<=total_stamps;l++) { UpdateBestComb(); }
关键还是我没利用好最多只有4张邮票这个条件,终于知道为什么那么多人说这是个水题了。在被此题困扰的时候我忘了一个基本原则:人类有寻求最简的天性。
ACM的题确实有趣,但确实很有挑战。一道题对于经过专业训练的人来说也许是小菜一碟,但对我们这些没受过训练,算法水品一般般,仅凭兴趣研究的人来说可能是一场头脑风暴。苦思冥想几天可能还想不出一个解决方案,即使好不容易憋出一个方案,也可能由于太繁琐而无法实现,或者勉强实现了也错误百出。ACMer不仅仅在基本算法运用方面灵活自如,比如贪心,回溯,动态规划等等,更重要的是,他们更善于用数学符号清晰地表达他们解题思路,比如用一个递推方程,方程写出来后,代码逻辑就清晰多了。我们普通算法爱好者的差距就在这里。这几天有点过分投入了,脑力消耗很大,现在需要心平气和,认清自己的现实情况。也许需要去啃一部《算法导论》之类的名著来打牢基础。无论如何,我会坚持下去。