背包問題
===
經典的背包問題有3種:
+ 01背包問題:
每<font color="#f00">**種**</font>物品個數有<font color="#f00">**只有1個**</font>
+ 無限/完全背包問題:
每<font color="#f00">**種**</font>物品個數有<font color="#f00">**無限多個**</font>
+ 有限/多重背包問題:
每<font color="#f00">**種**</font>物品個數有<font color="#f00">**有限定數量**</font>
當然有非這三種的噁心題,先會這三種基本上無敵了,後面會細講其他的變化題
## 01背包問題
例題
https://zerojudge.tw/ShowProblem?problemid=b184
01背包問題-每種物品個數有只有1個
每個物品都有重量
每個只能取一次
問:在背包容量有限制時能拿走最大的價值
### 講解
先把接著要用到的變數規定好
value | 價值
--- | ---
**weight**|**重量**
只考慮第1到i項物品,背包體積為n時的最大價值,紀錄為:
$$dp[i][n]$$
假設固定體積n的背包,且有k種物品考慮
可寫成(偽程式碼):
```cpp=
for(int i=0;i<k;i++){
for(int j=0;j<n;j++){
if(n>=weight[i]){
dp[i][n]=max(dp[i-1][n],dp[i-1][n-weight[i]]+value[i]);
}
else{
dp[i][n]=dp[i-1][n];
}
}
}
```
不過有這樣空間複雜度很大
要減少空間複雜度可以用滾動dp
我們可以把轉移式寫成:
>$$dp[j]=max(dp[j],dp[j-weight[i]+value[i])$$
這樣不只空間複雜度變小很多
轉移式看起來也更乾淨易懂了
以下模板為偽程式碼
```cpp=
for(int i=0;i<物品數量;i++){
for(j=背包最大承重;j>0;j--){
if(j-weight[i]>=0){ //講白話一點:背包還能裝東西就更新
dp[j]=max(dp[j],dp[j-weight[i]]+value[i]); //dp[j]表示在背包最大承重為j時所能有的最大價值
}
}
}
```
### 範例練習
zerojudge:b184: 5. 裝貨櫃問題
https://zerojudge.tw/ShowProblem?problemid=b184
AC(2ms, 336KB)
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
#define inf 1e9
#define maxn 10000005
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int n;
int w=100;
while(cin>>n){
int weight[105];
int value[105];
int dp[105]={0};
for(int i=1;i<=n;i++){
cin>>weight[i]>>value[i];
}
for(int i=1;i<=n;i++){
for(int j=100;j>0;j--){
if(j-weight[i]>=0){
dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);
}
}
}
cout<<dp[100]<<endl;
}
}
//b184
```
UVa:10130 - SuperSale
https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=1071
zerojudge:f440: 10130 - SuperSale
https://zerojudge.tw/ShowProblem?problemid=f440
(700ms)
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
#define inf 1e12
int value[1005];
int weight[1005];
int dp[1005];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int k;
cin>>k;
for(int i=1;i<=k;i++){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>value[i]>>weight[i];
}
int m;
cin>>m;
int cnt=0;
while(m--){
int v;
cin>>v;
memset(dp,0,sizeof(dp));
for(int i=1;i<=n;i++){
for(int j=v;j>0;j--){
if(j-weight[i]>=0){
dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);
}
}
}
cnt+=dp[v];
}
cout<<cnt<<endl;
}
}
```
### 其他類題
zerojudge:a587: 祖靈好孝順 ˋˇˊ
https://zerojudge.tw/ShowProblem?problemid=a587
洛谷:P1048 [NOIP2005 普及组] 采药
https://www.luogu.com.cn/problem/P1048
## 無限/完全背包問題
例題
https://www.luogu.com.cn/problem/P1616
無限背包問題-每種物品個數有無限個
每種物品都有重量
問:在背包容量有限制時能拿走最大的價值
### 講解
value | 價值
--- | ---
**weight**|**重量**
無限背包其實就只是可以挑同物很多次的01背包問題
用滾動dp寫出來的轉移式都是:
>$$dp[j]=max(dp[j],dp[j-weight[i]+value[i])$$
>
但在程式碼上有一個地方不同
應該看的出來有不一樣的地方
```cpp=
//01背包
for(int i=0;i<物品數量;i++){
for(int j=背包最大承重;j>0;j--){
if(j-weight[i]>=0){ //背包還有空間時
dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);
}
}
}
```
```cpp=
//無限背包
for(int i=0;i<物品數量;i++){
for(int j=1;j<=背包最大承重;j++){
if(j-weight[i]>=0){ //背包還有空間時
dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);
}
}
}
```
你會看到轉移式都一樣
不過無限背包要從1開始
無限背包其實就是找"性價比高的"感覺
最小的重量但有最大的價值
### 範例練習
洛谷:P1616 疯狂的采药
https://www.luogu.com.cn/problem/P1616
(AC 83ms)
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
#define inf 1e9
#define maxn 10000005
int weight[10005]; //time limit,時間限制,把這當背包容量
int value[10005];
int dp[10000005];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int t,m;
cin>>t>>m;
for(int i=0;i<m;i++){
cin>>weight[i]>>value[i];
}
for(int i=0;i<m;i++){
for(int j=1;j<=t;j++){
if(j-weight[i]>=0){
dp[j]=max(dp[j],dp[j-weight[i]]+value[i]);
}
}
}
cout<<dp[t]<<endl;
}
```
### 其他類題
## 有限/多重背包問題
例題
https://tioj.ck.tp.edu.tw/problems/1387
有限背包問題-每種物品個數有限
每種物品都有重量
問:在背包容量有限制時能拿走最大的價值
### 講解
value|價值
--- | ---
**weight**|**重量**
**num**|**數量**
有限背包就是每種物品都限量時
背包最大能裝多少東西
以下是轉移式
k表示該物選用的數量
>$$dp[j]=max(dp[j-weight[i]*k]+value[i]*k)$$
模板偽程式碼:
```cpp=
for(int i=0;i<物品數量;i++){
for(int j=背包最大承重;j>0;j--){
for(int k=1;k<該物品有的數量;k++){
if(j-weight[i]*k>=0){ //背包還有空間時
dp[j]=max(dp[j],dp[j-weight[i]*k]+value[i]*k);
}
}
}
}
```
### 範例練習
tioj:1387. Striker的秘密
https://tioj.ck.tp.edu.tw/problems/1387
(Results of submission #274841 AC 732ms)
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
#define inf 1e9
#define maxn 10000005
int dp[10005];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int n;
cin>>n;
int weight[105];
int value[105];
int num[105];
for(int i=0;i<n;i++){
cin>>weight[i]>>value[i]>>num[i];
}
int t;
cin>>t;
for(int i=0;i<n;i++){
for(int j=t;j>0;j--){
for(int k=1;k<=num[i];k++){
if(j-weight[i]*k>=0){
dp[j]=max(dp[j],dp[j-k*weight[i]]+k*value[i]);
}
}
}
}
cout<<dp[t];
}
```
### 其他類題