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.坑点
- 使用递归会发生超时的问题—— 最后一个测试用例过不去
可能是我用的剪枝优化不够优化,所以仍然没有什么明显的效果。【不是剪枝问题  ̄へ ̄】
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
所对应的执行步骤【跟上述的程序不是同一个程序。这里只是做一下手动调试而已】