本文章的问题描述部分参考了http://blog.youkuaiyun.com/cugbliang/article/details/2742242#reply
问题重述:
给出n种邮票,每种邮票有自己的面值(面值可能重复)
指定m种“总面值”,对每种“总面值”,求解满足如下条件的组合以达到该“总面值”
(1) 所用邮票在n种中可以重复选取
(2) 所用邮票张数〈=4
(3) 尽量多的使用那个不同种类的邮票 Max (Stamp Types)
(4) 若有多种方案满足(3),则选取张数最小的一种方案 Min (Stamp Num)
(5) 若有多种方案满足(3)(4),则选取“最大面额”最高的一种方案。 Max(Heightest Value)
(6) 若有多种方案满足(3)(4)(5) 则输出 “tie”
题目分析:
(1) 算法定位:
从题目的条件可知,此题必须遍求所有方案以求解,因此采用搜索的方法是非常合理的,因为题目带由一定的递推性,也可以尝试动态规划的方法.
(2) 问题优化与简化
对于搜索型题目,关键的优化就是减少重复计算,本题可由3方面的简化,避免重复性计算.
(1) 对于面额相等的邮票,可以限制在1~5张,多余的可以不处理,例如这样的输入:
1 1 1 1 1 1 1 1 0 8个1
4 0
可以简化成为:
1 1 1 1 1 0 5个1
4 0
他们的解是相同的
(2) 搜索求解时约定, 后一张邮票面额>=前面张邮票的面额,即如下的6种情况:
1 2 3 2 3 1 3 12 1 3 2 3 2 1 2 1 3
只需搜索判断一种,即 1 2 3 的情况
(3) 对于给定的n种邮票,遍历4张邮票的所有可达“总面额”的解,当m种指定面额给出,只需插标输出即可,不需要重复m次类似的搜索。(因为m次的搜索中,很多是重复计算的)
(3)算法介绍
(1) 搜索方法一: (base on 史诗’s program)
主要思路:
4重循环,枚举所有可能,依据题目条件保留最优解。
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++)
{
更新最优解
}
优化:
使用优化方案(1)(2), 修改后可以加上优化方案(3)
评价:
编程复杂度低,代码少,运行时空效率高,比赛时候推荐使用
但是扩展性受限制,若题目给的是任一k张而不是4,则必须大量修改代码
(2) 搜索方法二: (base on hawking’s program)
主要思路:
递归深度搜索,遍历所有k张邮票的可达面额的解,保留每种面额的最优
解,查表输出指定面额的解,并给定k=4,即为题目要求的情况。
Void Solve(int deep)
{
更新当前方案所达面值的最优解
if (deep>4) return;
for(int s=now[deep-1];s<=total_stamp;s++)
{
now[deep]=s;
solve(deep+1);
}
}
优化:
使用优化方案(1)(2)(3)进行截枝
评价:
深度搜索的经典模板程序,推荐学习,几乎所有的搜索都可以套用的模板
编程复杂度中,运行时空效率高,通用性强一些,但代码可能稍为多一些
(3) 动态规划: (base on my program)
主要思路:
定义:int anstype[k][i][j] 表示:
用前k种邮票,选i张,达到面额为j,最多用多少种不同的类型邮票。
定义:bool tied[k][i][j] :
k,i,j 定义如上,tied 表示 anstype[k][i][j] 的最优值是否有多种方案.即同时满足题目的条件(3)(4)时是否多种
定义:bool anstied[k][i][j] :
由于 tied 限制不够强,必须多一个anstied 表示同时满足条件(3)(4)(5)是否有多种方案
递推关系:
(1)
anstype[k][i][j]=Max{anstype[k-1][i-w][j-w*value[k]]+1} 1<=w<=4, i-w>0, j-w*value[k]>=0
(2)
在anstype 的更新最大值的同时,可以判断,若某个w 使得
anstype[k][i][j]==anstype[k-1][i-w][j-w*value[k]]+1 则
tied[k][i][j]=ture;
anstype[k][i][j]<anstype[k-1][i-w][j-w*value[k]]+1 则
tied[k][i][j]=false
(3)
至于anstied 可以这样求得:
anstype[k][i][j]<anstype[k-1][i-w][j-w*value[k]]+1时
anstied[k][i][j]=tied[k-1][i-w][j-w*value[k]]
anstype[k][i][j]==anstype[k-1][i-w][j-w*value[k]]+1时
anstied=true;
主要优化:
使用优化方案(1)(2)(3)
空间优化,由于[k][i][j]只与[k-1][i][j]有关,所以只需2维数据[i][j]就可以了
这是动态规划最常用的空间优化,另外的一些是压缩存储技巧,主要的是用位运算存储,本题没有用上,不过以后有些题目会常用。
评价:
编程复杂度 中,运行时间少,可以承受大规模数据,规模可扩展,
但是空间要求量大,必须使用压缩技巧,这样编程复杂度会增大,推荐研究算法用,比赛不用
总结:
类似本题的问题很多,光stamps问题多年来就一直存在各种变种,sticks问题与本体类似,通用的算法都是搜索,练熟搜索模板是基础,简化问题、掌握剪枝技巧是解题关键。某些特殊问题可以用动态规划,但是并不是所有的都可以,例如sticks我试过是不能用动态规划的,而且比赛时候,思考会浪费时间,所以推荐比赛时用搜索。(不排除某些题目用搜索通过不了的大数据,必须用动态规划解决,所以要估计好时间空间,选用稳妥的算法)