PAT1 1068 Find More Coins

题目链接
我的github

题目大意

给出 N N N个硬币,要求从这 N N N个硬币中选出若干个硬币使选出的硬币值的和恰好为 M M M

输入

每组包含一个测试用例

  • 第一行是一个两个正数 N ≤ 1 0 4 N\le10^4 N104表示硬币的数量, M ≤ 1 0 2 M\le10^2 M102表示要求的硬币的值的和
  • 第二行是 N N N个正数,表示 N N N个硬币的值

输出

对每个测试用例,升序输出所选的硬币的面值

  • 如果不存在这样一组硬币使硬币的面值和恰好等于 M M M,就输出No Solution
  • 如果存在多组硬币序列,则输出硬币序列的字典最小的那一组

样例输入

8 9
5 9 8 7 2 3 4 1

4 8
7 2 4 3

样例输出

1 3 5

No Solution

解析

这题可用动态规划来解。
实际上这题可以看成是一个01背包的问题,且需要恰好装满。
即背包的容量就是 M M M,每个硬币的重量和面值相等。假设dp[i]表示背包容量是i时,所能装的最大硬币的价值,c[i]表示第i个硬币的价值,则动态转移方程式如下
d p [ i ] = m a x ( d p [ i ] , d p [ i − c [ i ] ] + c [ i ] ) dp[i]=max(dp[i], dp[i-c[i]]+c[i]) dp[i]=max(dp[i],dp[ic[i]]+c[i])
最后判断是否有解时,只需判断dp[m]是否等于m即可。
但是对于有多解的情况仅仅凭上面的几个数组是无法求出字典序最小的那一组。如何改进呢?

  • 先将硬币的值降序排列
  • 加一个flag[i][j]表示第i个硬币,容量是j的时候是否被选中,状态转移的方程式也要改一下
    for i in range(n):
        for j in range(m, 0, -1):
            if j >= c[i] and dp[j] <= dp[j - c[i]] + c[i]:
                dp[j] = dp[j - c[i]] + c[i]
                flag[i][j] = True

即当dp[j]==dp[j - c[i]] + c[i]时也要更新。这是因为我们之前将硬币降序排列了,如果在一个序列里面添加一个比所有元素都小的元素,那么这个序列会变得更小。
同时flag[i][j] = True记录是否选中第i个硬币,方便之后输出字典序最小的硬币组合

输出字典序最小的硬币组合的过程实际上就是逆着找的过程,这样就既保证了硬币面值的升序输出,又保证了硬币组合的字典序最小

# -*- coding: utf-8 -*- 
# @Time : 2019/6/27 16:51 
# @Author : ValarMorghulis 
# @File : 1068.py
def solve():
    n, m = map(int, input().split())
    c = list(map(int, input().split()))
    dp = [0 for i in range(m + 1)]
    flag = [[False for i in range(m + 1)] for j in range(n)]
    c.sort(key=lambda x: -x)
    for i in range(n):
        for j in range(m, 0, -1):
            if j >= c[i] and dp[j] <= dp[j - c[i]] + c[i]:
                dp[j] = dp[j - c[i]] + c[i]
                flag[i][j] = True
    if dp[m] != m:
        print("No Solution")
    else:
        i, j = n-1, m
        while i != -1 and j != 0:
            if flag[i][j]:
                j -= c[i]
                print(c[i], end=('\n' if j == 0 else ' '))
            i -= 1


if __name__ == "__main__":
    solve()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值