PAT 1068 Find More Coins C++版

1.题意

给出拥有的硬币数和待支付的金额,以及每枚硬币的价值。

现在欲让你求出需要什么样的硬币才能精确的支付金额。

2.分析

解答这题的方法有两种:

2.1 深搜

深搜的思想就是“放/不放” 两种选择,然后匹配出精确的金额即可。但是需要说明的是:如果这题使用深搜实现,好像最后一个测试用例会超时(最后一个测试用例只有1分)。

2.2 动态规划

暂未实现。

3.代码

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<functional>

#define maxn 10005
using namespace std;

int N , M;
int coins[maxn];
int rec[maxn];
int COUNT = 0;//解的个数
int res[maxn];//存下所有解
int num = 0;
int flag = 0;//表示是否存在解

//使用深搜算法解决这个和问题
//对于每枚硬币,都有放或者不放这两种选择,在dfs代码中实现这两种选择即可
void dfs(int root,int index,int sum){//root表示是0
if( sum > M){//如果超过了M
return ;
}
else if(sum == M){
if(COUNT == 0){
num = index;
for(int i = 0;i< index;i++){
res[i] = rec[i];//保存到res中
}
}
COUNT ++;//解数+1
flag = 1;
}
if(flag == 1) return ;

//继续往下递归 => 放硬币
rec[index] = root;

dfs(root+1,index+1,sum + coins[root]);
rec[index] = 0;//因为上面已经修改了值,所以这里要重置为0

//继续往下递归 => 不放硬币
if(root + 1 < N) dfs(root+1,index,sum);
}

int main(){
cin >> N >> M;
int i,j;

for(i = 0;i< N;i++){
cin >> coins[i];
}
sort(coins,coins+N,less<int>());

int index = 0;//下标
int sum = 0;//总和
for(i = 0;i< N;i++){
index = 0;//重置为0
sum = 0;//重置为0
dfs(i,index,sum);//从i开始
}

if(COUNT == 0 ){
cout << "No Solution\n";
return 0;
}
for(j = 0;j < num;j++){
if(j!=num-1) cout << coins[res[j]]<<" " ;
if(j == num-1) cout << coins[res[j]] ;
}
}

4.测试用例

给出如下几组测试用例:

8 9
5 9 8 7 2 3 4 1

4 8
7 2 4 3

4 8
7 2 4 4

4 8
7 1 4 4

5.坑点

  • 使用递归会发生超时的问题—— 最后一个测试用例过不去
    PAT 1068 Find More Coins C++版_测试用例
    可能是我用的剪枝优化不够优化,所以仍然没有什么明显的效果。【不是剪枝问题  ̄へ ̄】

== update on 2019 05 18 == 近些天在学习DP,在刷PAT的时候,看到这道题,就想当做DP练个手。在用DP之前,透露一点儿内容就是:即使这道题不用DP,用深搜也是可以解决的。用深搜可以解决这道题的主要一个原因是**“姥姥设计的测试用例不够好”**,对于上述没有过的测试用例如果我们在dfs之前调用如下的一个if判断即可:

if(temp < M){
cout <<"No Solution\n";
return;
}

这里的 ​​temp​​ 是整个序列的所有和。当然,这只是一个小伎俩,没有什么鸟用,对于这题还是应该用dp。

​===========================分割线============================​

对于这题应该很多人都会直觉上使用dfs,毕竟dp是什么鬼玩意儿。。。

看了别人写的代码之后,才知道这题是考dp,而且是考01背包那种dp。网上很多代码,相信如果没有接触过dp的人,大多数是看不懂的,在看解题思路之前,先做一下如下介绍:


  • ​w[maxn]​​表示的是每个硬币的价值
  • ​dp[maxn][maxn]​​​,其中​​dp[i][j]​​表示的是在添加前i项(包括第i项)物品,容量为j的情况下所能达到的最大价值
  • ​choice[maxn][maxm]​​​,​​choice[i][j]=1​​​表示的是第i项作为结果序列加入,​​choice[i][j]=0​​表示的第i项未加入。

对于动态规划可以解决的题的特征我就不再啰嗦了,可以详细见我的专题【updating】讲解。

我们视这个货币为重i且价值为i的货币,这样就可以把这个计算货币的问题,转化为背包问题了。【可能又有人问:背包里计算的是最大价值,这里计算的是固定价值,怎么行呢?我们这么想:背包计算的是体积v中最大的价值m;如果对于v/m=1的物品来说,只有装满,才能得到最大的价值v(=m),同理,我们这里只需要找出体积v时所能装到的最大的值v,并且判断这个v是不是输入中的总价值即可。于是问题就转化为 ​​=>​​ 求在体积v下的最大价值是否是v,如果是,则输出最优解序列】



step1:递归判断过程(其实动态规划就是这个递归判断式子是最重要的,其余的都是扯淡)无非如下两种:
(1)如果​​dp[i-1][j]​​(不放第i件物品的情况) >= ​​dp[i-1][j-w[i]] + w[i]​​(放第i件物品的情况),则​​dp[i][j] = dp[i-1][j-w[i]] + w[i]​​ (因为要放第i件物品,所以要为第i件物品腾出体积,当前的体积是j)
(2)如果​​dp[i-1][j]​​(不放第i件物品的情况) < ​​dp[i-1][j-w[i]] + w[i]​​(放第i件物品的情况),则​​dp[i][j] = dp[i][j]​



step2:在第一步更新的时候,如果有更新​​dp[i][j]​​,则将相应的​​choice[i][j]​​重置为1,否则置为0。



step3:判断​​dp[n][m]​​是否和m相等,如果不相等,则直接输出​​No Solution​​,否则执行第四步



step4:根据​​choice[][]​​数组,找出具体的最优解。寻找过程如下:
对于​​choice[k][v]​​(初始值:k=n,v=m),判断choice[i][j]是否是1,如果是1,表示的是加入了第i件物品,则将​​v-=w[i]​​(因为要回到体积为j-w[i]的地步),同时进行k–处理(因为当前的第i件物品处理完了,需要判断第k-1物品)。将整个​​choice[k][v]==1​​的k保存下来就是我们需要的解(需要始终记住:k指的是第k件物品放没放;v指的是该时的体积有多少。



针对上述四个步骤,得到的代码如下:

#include<iostream> 
#include<cstdio>
#include<algorithm>
#define maxn 10005
#define maxm 105

using namespace std;

int N,M;
int w[maxn];//总重量 == 总价值
int dp[maxn][maxm];//用于存储解的动态数组
int choice[maxn][maxm];//表示选择了哪个
int flag[maxn];//标记输出
int index = 0;

int cmp(int a,int b){
return a > b;
}

void solve(){
//对数组进行初始化操作
int i,j;
for(i = 1;i <=N ;i++ ){//表示当前的
for(j = 1;j <= M ;j++){//j表示 当前的容量
if(j < w[i]){//如果当前的容量小于w[i]
dp[i][j] = dp[i-1][j];
choice[i][j] = 0;
}else{
//如果放的时候大于等于不放的时候 ,则应该使用放的情况
if( (dp[i-1][j-w[i]] + w[i] ) >= dp[i-1][j]){
dp[i][j] = dp[i-1][j-w[i]] + w[i] ;//取两者较大值
choice[i][j] = 1; //标记i放进去了
}
else if((dp[i-1][j-w[i]] + w[i] ) < dp[i-1][j]) {
dp[i][j] = dp[i-1][j];
choice[i][j] = 0;
}
}
}
}
}

//找出解的过程
void find(){
int k = N,v= M;
while(k >= 0){
if(choice[k][v] == 1){//说明当前节点加入到了序列中
flag[k] = 1;
v -= w[k];
index++;
}
else{
flag[k] = 0;
}
k--;
}
}

int main(){
cin >> N >> M;
int i ,j;
fill(dp[0],dp[0]+maxn*maxm,0);

//输入数字 下标从1开始
for(i = 1;i <= N ;i++){
cin >> w[i];
}
sort(w+1,w+N+1,cmp);
solve();
find();
if(dp[N][M]!=M) cout <<"No Solution\n";
else{
for(i = N;i >=1; i-- ) {
if(flag[i] == 1) {
if( index > 1) cout << w[i] <<" ";
else cout << w[i];
index--;
}
}
}
}

如下给出测试用例

4 5
1 2 3 4

所对应的执行步骤【跟上述的程序不是同一个程序。这里只是做一下手动调试而已】

PAT 1068 Find More Coins C++版_#include_02