1. 程式人生 > >完全揹包----兩個for迴圈的先後問題

完全揹包----兩個for迴圈的先後問題

這篇文章主要是講完全揹包問題中,什麼情況下兩個for迴圈的位置不能交換。

點選開啟連結(完全揹包問題----思想的理解)中我們提到,完全揹包一維陣列的實現的兩個for迴圈是可以交換順序的。

虛擬碼分別如下所示:

方式一:《揹包九講》基於01揹包問題推匯出來的,還記得和01揹包一維陣列實現的區別嗎?

f[0] =0;

for    i:1->n

    do  for j:c[i]->C

          f[j] = max(f[j],f[j-c[i]+w[i]])

方式二:《演算法競賽入門》基於DAG推匯出

f[0] = 0

for   i:1->C

      do for j:1->n

          if(i>=c[j] && f[i]>f[i-c[j]]+w[i])  f[i] =f[i-c[j]]+w[i];

完全揹包問題的要求是要求求出裝滿揹包時的最大重量,在這種情況下,上述兩種for迴圈都是可以的。

但是我們現在來換一個問題:UVa中的674硬幣找零問題:點選開啟連結

該問題大意如下:

有5種硬幣,面值分別為1,5,10,20,25。給你一個總額s,問有多少種找零方式?

注意比如總額為6,則[1,5],[5,1]顯然是同一種找零方式,這兩種只能算作一種。

問題的分析:

很顯然這是一個完全揹包模型,但是又不完全相同,因為它要求的是總的找零方式。

換成DAG的描述就是,以前要求從狀態S->到狀態0的最大路徑,而現在問你一共能有多有條路徑能從狀態S到狀態0.而且與點的順序無關。

狀態轉移方程很好定義

d[i][j]表示前i種硬幣對金額j找零的方式數。則

d[i][j] = d[i-1][j]+d[i-1][j-kc[i]]   k:0->j/c[i]

在這種情況下只能採用方式一,把對n的歷遍放到第一層迴圈,這樣才能避免把[1,5]、[5,1]算作兩條路徑。因為你限制了1,5的順序,

到了i=5之後不可能在發生5,1的情況產生。

對於方式二,把對n的歷遍放在第二層,對於任意的一個狀態v,都可能歷遍每一種硬幣,會導致重複冗餘的問題。

如果想加深理解,建議最好把兩種方式都實現一下,單步執行檢視

 該問題的具體程式碼實現如下:

int c[5] = { 1, 5, 10, 25, 50 };
int d[7490];

int main()
{
	d[0] = 1;
	for (int j = 0; j<5; ++j){
		for (int i = c[j]; i <= 11; ++i){
			d[i] += d[i - c[j]];
		}
	}
	int n;

	cin >> n;
	cout << d[n];


	return 0;
}