https://blog.csdn.net/waterboy_cj/article/details/81103479
问题描述:
有n种硬币,面值分别为v1,v2,v3...vn,每种硬币有无限多,给定非负整数s,可以选用多少个硬币,使得面值之和恰好为s?输出硬币数目的最小值和最大值,并且输出各自的选取方案(如果有多种方案,则输出硬币编号字典序较小的方案,输出每种选取方案的面值)。
【分析】本质上市一个DAG上的路径问题,我们把每种面值看做一个点,表示还需凑足的面值,则初始状态为0,目标状态为0,若当前在i,则每使用一枚硬币j,状态转移到i-vj。
详情见代码注释
#include <bits/stdc++.h>
using namespace std;
const int maxn = 110;
const int INF = 0x3f3f3f3f;
int d[maxn], vis[maxn], V[maxn], n, S;
int dpmax(int S)//求最多所需硬币书目
{
if (vis[S])return d[S];//如果S点计算过了,那么直接返回
vis[S] = 1;//标记已经计算过了S点
int &ans = d[S];//建立d[S]引用 方便下面对d[S]的操作
ans = -INF;
for (int i = 1; i <= n; i++)//遍历所有可用的硬币
if (S >= V[i])//当可用硬币比目标S小
ans = max(ans, dpmax(S - V[i]) + 1);//减去V[i]后(即多了一枚价值V[i]的硬币),与ans相比的最大值
return ans;//如果不可能达到,那么返回-INF,能达到即返回相应最大所需硬币数
}
int dpmin(int S) //同上
{
if (vis[S]) return d[S];
vis[S] = 1;
int &ans = d[S];
ans = INF;//就是此处不可能的值修改了一下
for (int i = 1; i <= n; ++i)
{
if (S >= V[i]) ans = min(ans, dpmin(S - V[i]) + 1);
}
return ans;
}
void print_ans(int s)
{
for (int i = 1; i <= n; i++)//遍历所有点
{
if (s >= V[i] && d[s] == d[s - V[i]] + 1)//已知所需硬币书目,
//那么当这个一个点的数目少了V[i]时的数目正好是 答案所需数目-1 那么就打印该点
{//printf("%d:%d -- %d:%d\n",s,d[s],s-V[i],d[s-V[i]]);
printf("%d ", V[i]);
print_ans(s - V[i]);
break;
}
}
}
int main()
{
cin >> n >> S;
for (int i = 1; i <= n; ++i)
cin >> V[i];
memset(vis, 0, sizeof(vis));
vis[0] = 1;//默认0点已经计算过
d[0] = 0;//0的时候就达成所需,下面计算的递归的终点
cout << dpmax(S) << endl;
print_ans(S);
printf("\n");
memset(vis, 0, sizeof(vis));
vis[0] = 1;
d[0] = 0;
cout << dpmin(S) << endl;
print_ans(S);
printf("\n");
return 0;
return 0;
}