1. 程式人生 > 其它 >揹包九講

揹包九講

揹包九講專題_嗶哩嗶哩_bilibili

一,01 揹包問題

題目:2. 01揹包問題 - AcWing題庫

題解:

二維程式碼:

  f [ i ][ j ] 表示只考慮到前 i 個物品,且總體積恰好是 j 的情況下,總價值最大是多少

  遞推式:

    情況①:不選第 i 個物品,f [ i ][ j ] =f [ i - 1 ][ j ]

    情況②:選第 i 個物品,f [ i ][ j ] =f [ i - 1 ][ j - v[i] ] + w[ i ]

    f [ i ][ j ] = max(①,②)

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#define N 1010
int n, m;    // 物品數,揹包體積
int f[N][N];
int v[N], w[N]; // 物品體積,物品價值
int max(int a, int b)
{
	return a > b ? a : b;
}
int main(void)
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)
		scanf("%d%d", &v[i], &w[i]);

	for (int i = 1; i <= n; i++)
	{
		for (int j = 1; j <= m; j++)
		{
			f[i][j] = f[i - 1][j]; // 預設不選
			if (j >= v[i]) // 如果可以選,即揹包體積大於物品體積
				f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
		}
	}

	printf("%d\n", f[n][m]);

	system("pause");
	return 0;
}

解析:

			f[i][j] = f[i - 1][j]; // 預設不選
			if (j >= v[i]) // 如果揹包體積大於物品體積
				f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);

上面這段程式碼就是從第 i-1 個物品的狀態,推導第 i 個物品的狀態的遞推式。相較於上文 “選於不選” 的判斷條件,這裡省略了 “不選“ 的判斷條件,並在可以選擇的時候,比較選與不選各自的價值。也可以理解為:不能選的話,只能不選;可以選的話,就要看選與不選哪個價值大咯。

二維壓縮為一維:01 揹包的空間優化

  f [ j ] 表示 如果當前考慮的是 i 個物品,那麼在總體積恰好是 j 的情況下,只考慮到前 i - 1 個物品的總價值最大是多少

  遞推式:從第 i-1 個物品的狀態,推匯出第 i 個物品的狀態

    情況①:不選第 i 個物品,f [ j ] =f [ j ] ,(注意,雖然這裡等式左右一樣,但意義不一樣,等式左邊代表的是隻考慮到前 i 個物品,而等式右邊代表的是隻考慮到前 i - 1 個物品。下文會將這種從 只考慮到前 i - 1 個物品 到 只考慮到前 i 個物品 的變化叫重新整理。這也是為什麼第二層迴圈要從後往前迴圈的原因)

    情況②:選第 i 個物品,f [ j ] =f[ j - v[i] ] + w[ i ],(注意,f [ j ]代表的是隻考慮到前 i 個物品,f[ j - v[i] ] 代表的是隻考慮到前 i - 1 個物品。下文會將這種從只考慮到前 i - 1 個物品 到只考慮到前 i 個物品 的變化叫重新整理。這也是為什麼第二層迴圈要從後往前迴圈的原因)

    f [ i ][ j ] = max(①,②)

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#define N 1010
int n, m;    // 物品數,揹包體積
int f[N];
int v[N], w[N]; // 物品體積,物品價值
int max(int a, int b)
{
	return a > b ? a : b;
}
int main(void)
{
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++)
		scanf("%d%d", &v[i], &w[i]);

	for (int i = 1; i <= n; i++)
		for (int j = m; j >= v[i]; j--)
			f[j] = max(f[j], f[j - v[i]] + w[i]);

	printf("%d\n", f[m]);

	system("pause");
	return 0;
}

解析:

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

上面這段程式碼就是從第 i-1 個物品的狀態,推導第 i 個物品的狀態的遞推式。因為不選是 f[ j ] = f[ j ],沒有變化,所以雖然這裡沒有表示出不選的程式碼,但實際上還是有這個含義的。再來說一下不選,f[ j - v[ i] ] + w[ i ],問題就出在這個 f[ j - v[ i] ] 身上,因為如果我們 j 是正常的從小到大遞增的話,那麼 f[ j ] 肯定 還沒經過重新整理,但f[ j - v[ i] ] 就一定經過重新整理,所以此時f[ j - v[ i] ] 代表的是 “只考慮到前 i 個物品”,而不是我們需要的 “只考慮到前 i - 1 個物品”。那麼如何在一次 j 迴圈中,保證用到的f[ j - v[ i] ] 都沒有重新整理過呢?只能是從後往前迴圈了。♪(^∀^●)

所以,內層迴圈從二維壓縮到一維就是:

		for (int j = m; j >= 0; j--)
		{
			f[j] = f[j];  // 不選
			if(j >= v[i]) // 可以選,比較選與不選的價值
				f[j] = max(f[j], f[j - v[i]] + w[i]);
		}

進一步優化就是:

		for (int j = m; j >= v[i]; j--)
			f[j] = max(f[j], f[j - v[i]] + w[i]);

  

二,完全揹包問題

三,多重揹包問題

四,混合揹包問題

五,二維費用的揹包問題

六,分組揹包問題

七,揹包問題求方案數

八,求揹包問題的方案

九,有依賴的揹包問題

========== ========= ======== ====== ====== ===== ==== === == =