回憶一下01揹包的動態規劃狀態及狀態方程:
設揹包容量爲M,一共有N件物品,每件物品質量爲weight[i],每件物品的價值爲value[i]。
1) 子問題定義:dp[i][j]表示前i件物品中選取若干件物品放入剩餘容量爲j的揹包中所能得到的最大價值。
2) 根據第i件物品放或不放進行決策。
詳細細節就不再多說了,我們直接進入正題。
在這裏的話,最優方案總數就是物品總價值最大的方案數,那要怎麼求呢?
我們設kry[i][j]爲dp[i][j]的方案數,那麼最優方案總數就是kry[N][M],我們分析下怎麼求kry[i][j],對於01揹包來說:
經過max函數取最大值後,如果dp[i][j]==dp[i-1][j] 或者 dp[i][j]==dp[i-1][j-weight[i]]+value[i] ,那麼kry[i][j] = kry[i-1][j] 或者 kry[i][j] = kry[i-1][j-weight[i]]。也就是說,對於dp[i][j]==dp[i-1][j] 時,表明第i件不放入時價值更大,那麼到狀態[i][j]的方案數就應該等於到狀態[i-1][j]的方案數。同理,如果dp[i][j]==dp[i-1][j-weight[i]]+value[i],說明第i件放入時價值更大,此時到狀態[i][j]的方案數就應該等於到狀態[i-1][j-weight[i]]的方案數。
而若dp[i-1][j] ==dp[i-1][j-weight[i]]+value[i],],則說明即可以通過狀態[i-1][j]在不加入第i件物品情況下到達狀態[i][j],又可以通過狀態[i-1][j-C[i]]在加入第i件物品的情況下到達狀態[i][j],並且這兩種情況都使得價值最大且這兩種情況是互斥的,那麼此時kry[i][j] = kry[i-1][j] + kry[i-1][j-weight[i]](你可以這樣理解:兩種到目前的狀態[i][j]的方案都是最優解,那最優解的方案總數就是兩個最優解的方案數相加)。
還有一點需要注意的,就是kry[][]必須全部初始化爲1。因爲無論dp[i][j]中i, j的值是多少,dp[i][j]總是存在的,既然存在,那就說明至少有一種方案,因此初始化爲1。這裏可能有些難以理解,沒事,先記着,慢慢就會懂了。
“Talk is cheap, show me the code.”
for(int j=0; j<maxm; j++ )
kry[j] = 1;
memset(dp, 0, sizeof(dp));
for( int i=1; i<=n; i++ )
{
for( int j=1; j<=m; j++ )
{
dp[i][j] = dp[i-1][j];
kry[i][j] = kry[i-1][j];
if( j>=w[i] )
{
if( dp[i][j]<dp[i-1][j-w[i]]+1 )
{
dp[i][j] = dp[i-1][j-w[i]] + 1;
kry[i][j] = kry[i-1][j-w[i]];
}
else if( dp[i][j]==dp[i-1][j-w[i]]+1 )
kry[i][j] = kry[i-1][j] + kry[i-1][j-w[i]];
}
}
}
同理,也可以用一維重複數組來求最優方案總數:
fill(kry, kry+maxm, INF );
memset(dp, 0, sizeof(dp));
for( int i=1; i<=n; i++ )
{
for( int j=m; j>=w[i]; j-- )
{
if( dp[j]<dp[j-w[i]]+1)
{
dp[j] = dp[j-w[i]] + 1;
kry[j] = kry[j-w[i]];
}
else if( dp[j]==dp[j-w[i]]+1)
kry[j] = kry[j] + kry[j-w[i]];
}
}