题目大意:用n个硬币买价值为m的东西,输出使用方案,使得正好几个硬币加起来价值为m。从小到大排列,输出最小的那个排列方案
DP解法:
01背包问题,因为要输出从小到大的排列,可以先把硬币面额从大到小排列,然后用bool类型的choice[i][j]标记数组dp[i][j](dp[i][j]的理解是前i个硬币组成面额为j的值)是否选取,如果选取了就令choice为true;然后进行01背包问题求解,如果最后求解的结果不是恰好等于所需要的价值的,就输出No Soultion,否则从choice[i][j]判断选取的情况,index从n到1表示从后往前看第i个物品的选取情况,v从m到0表示从容量m到0是否选取(v = v– w[index]),把选取情况压入arr数组中,最后输出arr数组
#include <iostream> #include <vector> #include <algorithm> using namespace std; int dp[10010], w[10010]; bool choice[10010][10010]; int cmp1(int a, int b){return a > b;}//由大到小排序 int main() { int n, m; scanf("%d%d", &n, &m); for(int i = 1; i <= n; i++) scanf("%d", &w[i]); sort(w + 1, w + n + 1, cmp1); for(int i = 1; i <= n; i++) { for(int j = m; j >= w[i]; j--) { if(dp[j] <= dp[j-w[i]] + w[i]) { choice[i][j] = true; dp[j] = dp[j-w[i]] + w[i]; } // printf("%d ", dp[j]); } // printf("\n"); } // for(int i=1; i<=n; ++i) // { // for(int j=1; j<=m; ++j) // printf("%d ", choice[i][j]); // printf("\n"); // } // for(int k=1; k<=m; ++k) // printf("%d ", dp[k]); // printf("\n"); if(dp[m] != m) printf("No Solution"); else { vector<int> arr; int v = m, index = n; while(v > 0) { if(choice[index][v] == true) { arr.push_back(w[index]); v -= w[index]; } index--; } for(int i = 0; i < arr.size(); i++) { if(i != 0) printf(" "); printf("%d", arr[i]); } } return 0; }
DFS解法:
1、找字典序最小的一种思路是将硬币按面值大小升序排序,这样我们dfs搜出的第一种情况便是答案(可以仔细想想为什么
2、如果就按上面的思路进行深搜会有几个测试点超时TAT,剪枝则是求出第一种情况后,后续搜索直接返回(通过设置标志位判断)。然而,即使是这样在最后一个测试点还是会超时,一个原因是数据量比较大,很皮的是这个情况根本没有solution,于是特判就好啦。求出所有硬币的面值总和sum,如果sum<m,则直接输出”No Solution“
#include<bits/stdc++.h> using namespace std; const int maxn = 1e4+10; int coin[maxn];//存储金币价值的数组 int n,m; bool vis[maxn]={false};//深搜时标记数组 bool flag = false;//递归边界标志 vector<int> ansPath;//保存ans int k;//k用来存储能用到的最大的金币的价值 void dfs(int cur, vector<int> path, int w)//参数1 表示 第i枚金币;参数2 表示 ans数组;参数3表示已经组合到的金币的价值 { if(flag) return;//如果flag为 true,直接结束。 vis[cur] = true;//将当前金币标记 为已访问(上次递归前已经加入到path数组 25行) //初始时cur为0,这个0号金币没加入path数组(前面没递归调用)。 if(w > m) return;//组合价值超过 要买的商品的价值m,直接结束 if(w == m) { flag = true;//此处为设置递归结束标志 ansPath = path;//找到题解,将其复制到ansPath全局数组中 return; } for(int i = 1; i<=k; i++) { if(!vis[i])// 如果vis[i] 为 false { path.push_back(coin[i]); dfs(i, path, w+coin[i]);//递归 vis[i] = false; path.pop_back(); } } } int main() { scanf("%d%d", &n, &m); long sum = 0; for(int i = 1; i<=n; i++) { scanf("%d", &coin[i]); sum += coin[i]; } if(sum < m) printf("No Solution"); else { sort(coin+1, coin+n+1); k = n; for(int i = 1; i<=n; i++) if(coin[i]>=m) { k = i;//面额比 第k个金币 大的金币都用不到,即k后面的金币都用不到!!! break; } dfs(0, ansPath, 0); if(flag)//如果flag为 true,说明找到满足要求的序列了!!! { printf("%d", ansPath[0]); for(int i = 1; i<ansPath.size(); i++) printf(" %d", ansPath[i]); } else printf("No Solution"); } return 0; }