较难的动态规划问题——付款问题,面值任意,可找零

来源:互联网 发布:知乎欧美乐坛 编辑:程序博客网 时间:2024/05/11 15:44

        在超市买东西经常会遇到付款、找钱的问题。

        一般来说,我们的货币面值也就 100, 50, 20, 10, 5, 1, 0.5, 0.1 这几种,基本都是单位1或者是5的倍数。如果问:最少要付多少张钞票?这个问题是比较容易的,只要从大往小付款就可以了。

        进一步如果可以让服务员找零,并且找零的钞票数量也算在总钞票数里,让总钞票数最少。比如付98元,只需要付100元找1+1元,一共三张,而不需要50+20+10+5+1+1+1 七张。这个问题稍有难度,可以思考一下。


        由于今天在一个python网站上做题,没仔细看题目。以至于花了一天时间做出来了这个问题:

        给定一组任意面值的钞票,以及要付款的钱数,在可以找零钱的条件下(找零的钞票张数也算在使用的钞票张数里),求最少一共使用多少张钞票? 

      找零所使用的钞票面值,也在给定的面值里。比如说,给定面值[100, 99],付款50元,最佳方案是 支付99元50张,找零100元49张,一共用了10张。


————————————————以下剧透答案————————————————————————————————


        反反复复想错了好几次,最后发现一开始写的代码基本上是对的。难点是roof上限的确定,以及能否支付的判断

        想清楚鸟,给定的面值,所能支付的最小定价,是由它们的公共最大公约数决定的!这样一来,能否支付以及支付张数的上限(roof)也就可以提前知道了,方便后面的搜索。想明白了还真简单啊。

def gcd(a,b):while b!=0:a,b = b,a%breturn adef checkio(data):'return minimal amount of used coins and notes.'d = {}l, dest = data# if l has float, amplify the numbers to integerl_dot = [len(str(i)) -1 - str(i).find('.') for i in l if int(i)!=i]if len(l_dot) != 0:l = [i*10**max(l_dot) for i in l]dest *= 10**max(l_dot)# use gcd, we can calc the minimal value that can be paid.mini = reduce(gcd, l)if dest % mini != 0:# This value can't be paid.return 0roof = dest / minifor i in l:d[i] = 1cur = 0while True:cur += 1for cur1 in xrange(1, cur+1):for i in sorted(d.keys()):if d[i] >= roof:continuefor j in l:if i + j == cur1 and ( not d.has_key(cur1) or d[cur1]>d[i]+1 ):d[cur1] = d[i] + 1if cur1 == dest and ( d[cur1]<roof ):roof = d[cur1]for cur2 in xrange(cur, 0, -1):for i in sorted(d.keys(), reverse=True):if d[i] >= roof:continuefor j in l:if i - j == cur2 and ( not d.has_key(cur2) or d[cur2]>d[i]+1 ):d[cur2] = d[i] + 1if cur2 == dest and ( d[cur2]<roof ):roof = d[cur2]if d.has_key(cur) and d[cur]>=roof:breakprint dprint d[dest]return d[dest]if __name__ == '__main__':#You give 100$ and get in change 1$assert checkio([[100,50,20,10,5,1],99]) == 2, 'First'#You can't give change#assert checkio([[50,20,5],41]) == 0, 'Second'# You give 2 times 20 and  1 or 50 and 1 and get 10 in change.#Other combinations are posible but you will use more then 3 notes.assert checkio([[50,20,10,5,1,0.5],41])==3, 'Third'assert checkio([[50, 20, 10, 5, 1], 78])  == 5 , 'Fourth'assert checkio([[1], 78])  == 78 , 'Fifth'checkio([[7,11,13], 1])checkio([[100,98], 1])

剩余的工作留给未来~~:

        既然已知给定面值所能组成的最小价格,那么通过搜索就能把“基础面额”算出来,然后用“基础面额”来做下一步:用正向搜索来支付价格,不用考虑找零。

        这样的话主循环就会简单的多啦。

        有空搞出来吧~


————————————————以下是之前犯的错误,心路历程————————————————————————

        一开始我觉得这道题非常难,在“装包问题”的基础上(见之前的博文《DP为王》),不断的正向、反向双向搜索,非常复杂,而且似乎结果还没弄对。

        后来在思考这样一个子问题时,有了灵感。

        子问题:给定一系列钞票和价格,理论上能否支付?

        不能支付是为什么呢?是因为有可能无论怎么找零都无法支付,比如给你的面值都是2、10、20之类的偶数,而付款是奇数,你就被坑了  :)


        之前认为是错的地方整个都不对{

(之前有一个错误想法,正写这个博客时候纠正过来了。以前我认为 价格 能被“最小面值”整除,此价格就能被支付,否则不行。其中“最小面值” 是能够通过加减组合得到的最小面值。比如上面举的例子[100,99],由于100-99=1,所以实际上所有正整数都可以被支付。

        而我在算[7, 11, 13]时候发现不对。[7,11,13]能否支付的最小面额是2(此处仍然是错的,可以付1元,只需要付7+7,找13元即可),但是它却可以用来支付7、11、13、9、15等等。可以试试看。


        ()










原创粉丝点击