题目大意:用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;
}