题目大意
给出 N N N个硬币,要求从这 N N N个硬币中选出若干个硬币使选出的硬币值的和恰好为 M M M
输入
每组包含一个测试用例
- 第一行是一个两个正数 N ≤ 1 0 4 N\le10^4 N≤104表示硬币的数量, M ≤ 1 0 2 M\le10^2 M≤102表示要求的硬币的值的和
- 第二行是 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[i−c[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()