貪心+基礎動態規劃(dp)+簡單STL運用(棧.隊列)--(2)

視頻講解<<](https://www.bilibili.com/video/av86427339/?spm_id_from=333.788.videocard.10)
https://www.bilibili.com/video/av86427339/?spm_id_from=333.788.videocard.10
ACM題目鏈接
https://vjudge.net/contest/353904#overview

0 1揹包問題

問題 :

有n件物品(每種物品都只有一件),w[i]表示物品的重量,v[i]表示物品的價值,現有一個容量爲V的揹包,應該如何選物品使得書包內裝的物品的value之和最大呢?

爲方便講解和理解,下面講述的例子均先用具體的數字代入,即:eg:number=4,capacity=8

int w[5]={0,2,3,4,5};//商品體積
int v[5]={0,3,4,5,6};//商品價值

總體思路
根據動態規劃解題步驟(問題抽象化、建立模型、尋找約束條件、判斷是否滿足最優性原理、找大問題與小問題的遞推關係式、填表、尋找解組成)找出01揹包問題的最優解以及解組成,然後編寫代碼實現。

動態規劃的原理
動態規劃與分治法類似,都是把大問題拆分成小問題,通過尋找大問題與小問題的遞推關係,解決一個個小問題,最終達到解決原問題的效果。但不同的是,分治法在子問題和子子問題等上被重複計算了很多次,而動態規劃則具有記憶性,通過填寫表把所有已經解決的子問題答案紀錄下來,在新問題裏需要用到的子問題可以直接提取,避免了重複計算,從而節約了時間,所以在問題滿足最優性原理之後,用動態規劃解決問題的核心就在於填表,表填寫完畢,最優解也就找到。

最優性原理是動態規劃的基礎,最優性原理是指“多階段決策過程的最優決策序列具有這樣的性質:不論初始狀態和初始決策如何,對於前面決策所造成的某一狀態而言,其後各階段的決策序列必須構成最優策略”。
揹包問題的解決過程
在解決問題之前,爲描述方便,首先定義一些變量:Vi表示第 i 個物品的價值,Wi表示第 i 個物品的體積,定義V(i,j):當前揹包容量 j,前 i 個物品最佳組合對應的價值,同時揹包問題抽象化(X1,X2,…,Xn,其中 Xi 取0或1,表示第 i 個物品選或不選)。

1、建立模型,即求max(V1X1+V2X2+…+VnXn);

2、尋找約束條件,W1X1+W2X2+…+WnXn<capacity;

3、尋找遞推關係式,面對當前商品有兩種可能性:

包的容量比該商品體積小,裝不下,此時的價值與前i-1個的價值是一樣的,即V(i,j)=V(i-1,j);
還有足夠的容量可以裝該商品,但裝了也不一定達到當前最優價值,所以在裝與不裝之間選擇最優的一個,即V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}。
其中V(i-1,j)表示不裝,V(i-1,j-w(i))+v(i) 表示裝了第i個商品,揹包容量減少w(i),但價值增加了v(i);

由此可以得出遞推關係式:

j<w(i) V(i,j)=V(i-1,j)
j>=w(i) V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}
這裏需要解釋一下,爲什麼能裝的情況下,需要這樣求解(這纔是本問題的關鍵所在!):

可以這麼理解,如果要到達V(i,j)這一個狀態有幾種方式?

肯定是兩種,第一種是第i件商品沒有裝進去,第二種是第i件商品裝進去了。沒有裝進去很好理解,就是V(i-1,j);裝進去了怎麼理解呢?如果裝進去第i件商品,那麼裝入之前是什麼狀態,肯定是V(i-1,j-w(i))。由於最優性原理(上文講到),V(i-1,j-w(i))就是前面決策造成的一種狀態,後面的決策就要構成最優策略。兩種情況進行比較,得出最優。

4、填表,首先初始化邊界條件,V(0,j)=V(i,0)=0;

在這裏插入圖片描述

然後一行一行的填表:

如,i=1,j=1,w(1)=2,v(1)=3,有j<w(1),故V(1,1)=V(1-1,1)=0;
又如i=1,j=2,w(1)=2,v(1)=3,有j=w(1),故V(1,2)=max{ V(1-1,2),V(1-1,2-w(1))+v(1) }=max{0,0+3}=3;
如此下去,填到最後一個,i=4,j=8,w(4)=5,v(4)=6,有j>w(4),故V(4,8)=max{ V(4-1,8),V(4-1,8-w(4))+v(4) }=max{9,4+6}=10……
所以填完表如下圖:
在這裏插入圖片描述
5、表格填完,最優解即是V(number,capacity)=V(4,8)=10。

核心代碼實現二維數組

for(int i=1;i<=4;i++)
	{
		//四個物品
		for(int j=0;j<=bagV;j++)
		{
			if(j<w[i])//如果放不進去,dp[i][j]就等於i-1個物品的價值
				dp[i][j]=dp[i-1][j];
			else//如果可以放進入就要判斷放進去好還是不放進去好(0 1揹包問題),取最大值
				dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
		}
	}

一維數組實現

01揹包還可以用一維數組實現,只不過此時的遞推式 & 初始條件就需要做些改變了。要想用一維數組存放所有狀態,也就是讓該數組某個時間是第 i-1 層的狀態,而過一段時間之後則成爲第 i 層的狀態。如下面所演示的,初始狀態下,一維數組 maxValue[ ]存放的是 i = 1 時的狀態值(對應上面的F[ 1 ][ j ],j = 0,1,2,…,W)

在這裏插入圖片描述

而當 i = 2 時,我們就需要計算 第二行的狀態值,並把它們覆蓋到maxValue[ ]一維數組之上。

問題是怎麼覆蓋呢?如果我們還是跟二維數組一樣從前往後遍歷數組,覆蓋的過程中某一時刻如下圖所示,其中數組前面部分是屬於 i=2 層的狀態值,後面部分屬於 i=1 層的狀態值。

在這裏插入圖片描述

但是,當我們繼續計算並寫入 i=2 的狀態值時,很有可能用到 i=1 的某個狀態值,而這個狀態值卻已經被覆蓋掉了,比如,我們計算 i=2 的maxValue[ 6 ]時,要找到 i=1 的 maxValue[ 3 ] 狀態值,本來它應該爲0,但卻變成4,如果我們用4去計算

maxValue[ 6 ],就會得到錯誤的結果。

在這裏插入圖片描述
改寫之後,原來maxValue[ W ]的值就由3變爲4。接着,改寫maxValue[ W-1 ],由於計算 i=2 的maxValue[ W-1 ] 不需要用到 i=1 的maxValue[ W ]狀態,所以,maxValue[ W ]的改動不影響maxValue[ W-1 ]的計算。

在這裏插入圖片描述

因此用一維數組實現必須是逆序

一維數組核心代碼

for (int i=1; i<=n; i++) 
{
			for (int j=bagv; j>=w[i]; j--)
			{
			//TM可算是明白了爲什麼要逆序,dp[j]只與它本身有關(就是上一層揹包狀態)還有前面某一個狀態值有關
				dp[j] = max(dp[j], dp[j-w[i]] + v[i]);
			}

代碼實現二維數組

爲了和之前的動態規劃圖可以進行對比,儘管只有4個商品,但是我們創建的數組元素由5個。

#include <stdio.h>
#include <string.h>
#include <algorithm>
#include <iostream>
using namespace std;

int main()
{
	int w[5]={0,2,3,4,5};//商品體積
	int v[5]={0,3,4,5,6};//商品價值
	int bagV=8;//揹包大小
	int dp[5][9];
	memset(dp,0,sizeof(dp));
	for(int i=1;i<=4;i++)
	{
		//四個物品
		for(int j=0;j<=bagV;j++)
		{
			if(j<w[i])//如果放不進去,dp[i][j]就等於i-1個物品的價值
				dp[i][j]=dp[i-1][j];
			else//如果可以放進入就要判斷放進去好還是不放進去好(0 1揹包問題),取最大值
				dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
		}
	}
	for (int i = 0; i < 5; i++)
	{
		//查看數字dp
		for (int j = 0; j <= bagV; j++)
			printf("%d ", dp[i][j] );
		printf("\n");
	}
	printf("%d\n", dp[4][8]);//輸出最優值
	return 0;
}

揹包問題最優解回溯

通過上面的方法可以求出揹包問題的最優解,但還不知道這個最優解由哪些商品組成,故要根據最優解回溯找出解的組成,根據填表的原理可以有如下的尋解方式:

V(i,j)=V(i-1,j)時,說明沒有選擇第i 個商品,則回到V(i-1,j);

V(i,j)=V(i-1,j-w(i))+v(i)時,說明裝了第i個商品,該商品是最優解組成的一部分,隨後我們得回到裝該商品之前,即回到V(i-1,j-w(i));

一直遍歷到i=0結束爲止,所有解的組成都會找到。

就拿上面的例子來說吧:

最優解爲V(4,8)=10,而V(4,8)!=V(3,8)卻有V(4,8)=V(3,8-w(4))+v(4)=V(3,3)+6=4+6=10,所以第4件商品被選中,並且回到V(3,8-w(4))=V(3,3);
有V(3,3)=V(2,3)=4,所以第3件商品沒被選擇,回到V(2,3);
而V(2,3)!=V(1,3)卻有V(2,3)=V(1,3-w(2))+v(2)=V(1,0)+4=0+4=4,所以第2件商品被選中,並且回到V(1,3-w(2))=V(1,0);
有V(1,0)=V(0,0)=0,所以第1件商品沒被選擇。
在這裏插入圖片描述

代碼實現

揹包問題最終版詳細代碼實現如下:

#include<iostream>
using namespace std;
#include <algorithm>
 
int w[5] = { 0 , 2 , 3 , 4 , 5 };			//商品的體積2、3、4、5
int v[5] = { 0 , 3 , 4 , 5 , 6 };			//商品的價值3、4、5、6
int bagV = 8;					        //揹包大小
int dp[5][9] = { { 0 } };			        //動態規劃表
int item[5];					        //最優解情況
 
void findMax() {					//動態規劃
	for (int i = 1; i <= 4; i++) {
		for (int j = 0; j <= bagV; j++) {
			if (j < w[i])
				dp[i][j] = dp[i - 1][j];
			else
				dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
		}
	}
}
 
void findWhat(int i, int j) {				//最優解情況
	if (i >= 0) {
		if (dp[i][j] == dp[i - 1][j]) {
			item[i] = 0;
			findWhat(i - 1, j);
		}
		else if (j - w[i] >= 0 && dp[i][j] == dp[i - 1][j - w[i]] + v[i]) {
		//*******想想爲什麼是********else if(......)而不是else,那是因爲max(dp[i-1][j],dp[i-1][j-w[i]]+v[i])不能確定那個大所以要判斷一下是否dp[i][j] == dp[i - 1][j - w[i]] + v[i],從而判斷選沒選
			item[i] = 1;
			findWhat(i - 1, j - w[i]);
		}
	}
}
 
void print() {
	for (int i = 0; i < 5; i++) {			//動態規劃表輸出
		for (int j = 0; j < 9; j++) {
			cout << dp[i][j] << ' ';
		}
		cout << endl;
	}
	cout << endl;
 
	for (int i = 0; i < 5; i++)			//最優解輸出
		cout << item[i] << ' ';
	cout << endl;
}
 
int main()
{
	findMax();
	findWhat(4, 8);
	print();
 
	return 0;
}

例題

題目鏈接
http://acm.hdu.edu.cn/showproblem.php?pid=2602

AC代碼

#include <iostream>
#include <cmath>
#include <algorithm>
#include <queue>
#include <cstring>
#define ll long long int
#define maxn 1005
using namespace std;
int v[maxn],w[maxn],dp[maxn];
int main()
{
    int t,n,V,i,j,ans;
    scanf("%d",&t);
    while(t--)
    {
        memset(dp,0,sizeof(dp));
        scanf("%d %d",&n,&V);
        for(i=0;i<n;i++)
            scanf("%d",&v[i]);
        for(i=0;i<n;i++)
            scanf("%d",&w[i]);
        for(i=0;i<n;i++)
            for(j=V;j>=w[i];j--)
                dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
        printf("%d\n",dp[V]);
    }
    return 0;
 
}

快來練一練吧

ACM題目鏈接
http://acm.hdu.edu.cn/showproblem.php?pid=2670

下面介紹完全揹包問題,看下一篇文章
https://blog.csdn.net/weixin_43732535/article/details/104189808

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章