1. 程式人生 > >動態規劃(二) 硬幣問題

動態規劃(二) 硬幣問題

2.2硬幣問題

2.2.1 問題簡述:n種硬幣,每種都有無限多,要求最少或最多選用幾個硬幣,能湊出給定整數。

2.2.2 問題分析:與巢狀矩形不同的是,硬幣問題是一個確定終點的最長路和最短路問題。要注意,該問題可能無解。

2.2.3 關鍵程式碼

int dp(int S){
	if(vis[S]) return d[S];
	vis[S] = 1;
	int &ans = d[S];
	ans = -(1<<30);
	for(int i = 1; i <= n; i++)
		if(S > V[i])
			ans = max(ans, dp(S - V[i]) + 1);
	return ans;
} 

注意與巢狀矩形的對比,硬幣問題有幾個特殊的情況。首先,路徑長度有可能為0,所以,不能再用0表示這個d值還沒有算過;其次,結點S不一定能夠到達結點0,所以,需要用特殊的d[S]來表示無法到達。在這個演算法中,我們用另外一個數組vis[i]表示狀態i是否被訪問過,vis[i]應被初始化0。並且,我們把ans初始化為一個很小的整數,代表“無法到達”的數值。

2.2.4 完整程式碼

#include<iostream>
using namespace std;
//這裡只列舉最少需要的錢幣數,類推即可
int V[20];
int n, S; 
int d[200]={0},vis[200] = {0};

int min(int x, int y)
{
	return x < y ? x : y;
}

int dp(int S)
{
	if(S == 0) return 0;
	if(vis[S]) return d[S];
	int &ans = d[S];
	ans = (1<<30);
	for(int i = 0; i < n; i++)
		if(S >= V[i]) 
			ans = min(ans, dp(S - V[i]) + 1);
	return ans;
}

void print_ans(int *d, int S){
	for(int i = 0; i < n; i++)
		if(S >= V[i] && d[S] == d[S - V[i]] + 1){
			cout << V[i] << ' ';
			print_ans(d, S - V[i]);
			break;
		}
}

int main()
{
 	cout << "輸入硬幣種數:"; 
	cin >> n;
	cout << "依次輸入其面值:" << endl;
	for(int i = 0; i < n; i++)
		cin >> V[i];
	cout << "輸入要湊的總面值:";
	cin >> S;
	cout << "最少需要的硬幣數為:" << dp(S) << endl << "它們分別是:";
	print_ans(d, S);
	return 0; 
}

1我們也可以不用遞迴,直接遞推即可(如果既要求最大值,也要求最小值,此方法適用)

for(int i = 0; i < S; i++)
	for(int j = 0; j < n; j++)
		if(i >= V[j]){
			minv[i] = min(minv[i], minv[i - V[j]] + 1);
			maxv[i] = max(maxv[i], maxv[i - V[j]] + 1);
		}

2有一種“空間換時間”的列印路徑方法,我們需要將遞推稍微改寫一下。

for(int i = 0; i <= S; i++)
	for(int j = 0; j <= n; j++)
		if(i >= V[j]){
			if(i >= V[j]){
				if(min[i] > min(i - V[j] + 1)){
					min[i] = min[i - V[j]] + 1;
					min_coin[i] = j;
				}
			}
		}
//這裡只需呼叫print_ans(min_coin, S)即可
void print_ans(int *d, int S){
	while(S){
		cout << d[S] << ' ';
		S -= V[d[S]];
	}
}

2.2.5 總結

DAG最長路和最短路都可以用記憶化搜尋和遞推兩種方式實現。列印時既可以根據d值重新計算出每一步的最優決策,也可以在動態規劃時“順便”記錄下每一步的最優決策。

這裡有兩種“對稱”的狀態定義方程:

狀態1:設d(i)為從i出發的最長路,則d(i) = max{d(j) + 1 | (i, j)E}

狀態2:設d(i)為以i結束的最長路,則d(i) = max{d(j) + 1 | (i, j)E}

實際上,硬幣問題如果使用狀態2,就和巢狀矩形問題一樣了,但這裡列舉的是狀態1的解答過程,起展示作用。