您的位置:首页 > 其它

PAT L3-001 凑零钱(01背包(布尔背包)+记录路径)

2018-03-23 23:26 253 查看

L3-001. 凑零钱

时间限制:200 ms

内存限制:65536 kB

代码长度限制:8000 B

判题程序:Standard

作者:陈越

韩梅梅喜欢满宇宙到处逛街。现在她逛到了一家火星店里,发现这家店有个特别的规矩:你可以用任何星球的硬币付钱,但是绝不找零,当然也不能欠债。韩梅梅手边有104枚来自各个星球的硬币,需要请你帮她盘算一下,是否可能精确凑出要付的款额。

输入格式:

输入第一行给出两个正整数:N(<=104)是硬币的总个数,M(<=102)是韩梅梅要付的款额。第二行给出N枚硬币的正整数面值。数字间以空格分隔。

输出格式:

在一行中输出硬币的面值 V1 <= V2 <= … <= Vk,满足条件 V1 + V2 + … + Vk = M。数字间以1个空格分隔,行首尾不得有多余空格。若解不唯一,则输出最小序列。若无解,则输出“No Solution”。

注:

我们说序列{A[1], A[2], …}比{B[1], B[2], …}“小”,是指存在 k >= 1 使得 A[i]=B[i] 对所有 i < k 成立,并且 A[k] < B[k]。

输入样例1:

8 9

5 9 8 7 2 3 4 1

输出样例1:

1 3 5

输入样例2:

4 8

7 2 4 3

输出样例2:

No Solution

问题分析

学长和窝说这是01背包的变种布尔背包…….

题意:给n个硬币,问能否组成价值m。

那该怎么设计状态呢?

我感觉就是01背包的思想。可以这样,第i个硬币能否组成m,如果第i个硬币可以组成m,那么状态dp[j-coin[i]肯定就为true,否则为false,即表示当前硬币不可达m。如果选第i个,那么问题就变成i-1个硬币组成v-coin[i]的问题,不选就是i-1个硬币组成v的问题。边界状态肯定是硬币价值为0啦,0对于m一定是可达的,所以要初始dp[0] = true;所以状态转移方程为

dp[i][j] = dp[i-1][j] | dp[i-1][j-coin[i]]; 或者简化为一维,

dp[j] = dp[j] | dp[j-coin[i]]; (0 <= i <= n-1, 0 <= j <= m)

剩下的就是路径记录问题了,只要记住当前状态是由哪个状态转移过来的即可,输出coin[i]

ok,接下来是AC code(^_^)

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const  int N = 1e4+3,M = 400;
bool path
[M],dp
;
int coin
;
bool cmp(int a,int b){
return a>b;
}

int main()
{
int n,m;
cin>>n>>m;
for(int i = 0; i < n; ++i){
cin>>coin[i];
}
dp[0] = true; //m+0一定还可以组成m,即0这个数可达(边界状态)
sort(coin,coin+n,cmp); //这里我们从大到小排序,方便输出,到后面如果有更小的方案会覆盖
for(int i = 0; i < n; ++i){
for(int j = m; j >= coin[i]; j--){
//            dp[j] = dp[j]|dp[j-coin[i]];
if(dp[j-coin[i]]){ // 如果当前选择当前硬币可组成m
dp[j] = true;
path[i][j] = true; //记录路径
}
}
}
if(dp[m]==false) cout<<"No Solution"<<endl;
else{
int i = n-1, j = m, flag = 0;
while (i>=0&&j>=0){
if(path[i][j]){ //当前状态可选
if(flag==0) { printf("%d",coin[i]); flag = 1;}
else
printf(" %d",coin[i]);
j -= coin[i]; //要清楚当前状态是怎么转移过来的
}
i--;
}
puts("");
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: