DAG上的动态规划------硬币问题

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;
}

猜你喜欢

转载自blog.csdn.net/waterboy_cj/article/details/81103978