完全揹包----兩個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;
}