# 背包問題
## info
背包問題就是一種解決,如何用有限空間取得最大價值的問題
而實際概念就是用小問題計算大問題的答案,是一種DP問題
## 01背包
[01背包題目](http://203.64.191.163/ShowProblem?problemid=a279)
物體數為N,背包容量為C,每個物品有一個Val,和一個Valume
開一個N * C的陣列,陣列的第[i][j]個就是在可以放第1~i個物品且在背包最大容量為j時的最大價值
我們每次更新f[i][j]時都有兩個選擇
1.不放第i個物品
2.清出之前的空間放入第i個物品
這兩個我們發現他都是小於目前更新的小問題
一個是f[i-1][j],一個是f[i-1][j-Volume]
轉移式:$f[i][j]=max(f[i-1][j],f[i-1][j-Volume[i]]+Value[i])$
```cpp=
for(int i = 0; i < n; i++){
for(int j = 0; j < c; j++){
f[i][j]=max(f[i-1][j],f[i-1][j-Volume[i]]+Value[i]);
}
}
```
```cpp=
#include<bits/stdc++.h>
using namespace std;
int main(){
int n, c;
cin >> n >> c;
vector<vector<int> > dp(n,vector<int>(c+1, 0));
int val, v;
cin >> val >> v;
for(int i = 1; i < c+1 ; i++){
if(i >= v)dp[0][i] = val;
}
for(int i = 1; i < n; i++){
cin >> val >> v;
for(int j = 1; j <= c; j++){
if(j - v < 0) dp[i][j] = dp[i-1][j];
else dp[i][j] = max(dp[i-1][j], dp[i-1][j-v] + val);
}
}
cout << dp[n-1][c] << '\n';
}
```
#### 空間優化
我們會發現這樣的空間複雜度很差
所以我們可以進行一些空間優化
我們在轉移式中可以發現,我們只會用到f[i-1][任意容量]的資料
小於i-1的資料都不會再用到
所以我們可以很簡單的直接用滾動DP的方式解決這個問題
也就是開一個2*C的陣列,輪流更新0和1
不過個方法雖然簡單但實作稍微複雜了一點
取的容量也都是小於等於當前容量的
所以其實我們可以反著更新
從容量為c更新到1
#### 輸入優化
輸入我們可以發現Volume和Value的的陣列完全沒有功能
所以只要開一個變數就好
轉移式:$dp[j] = max(dp[j], d[j - Volume] + Value)$
```cpp=
for(int i = 1; i <= n; i++){
int a, b;
cin >> a >> b;
for(int j = c; j >= b; j--){
dp[j] = max(dp[j], dp[j - b] + a);
}
}
```
```cpp=
#include<bits/stdc++.h>
using namespace std;
int main(){
int n, c;
cin >> n >> c;
vector<int > dp(c+1,0);
int val, v;
cin >> val >> v;
for(int i = 1; i < c+1; i++){
if(i >= v)dp[i] = val;
}
for(int i = 1; i < n; i++){
cin >> val >> v;
for(int j = c; j >= 1; j--){
if(j - v < 0) dp[j] = dp[j];//如果把上面的j>=1改成j>=v,這行甚至可以刪掉
else dp[j] = max(dp[j], dp[j-v] + val);
}
}
cout << dp[c] << '\n';
}
```
## 有限背包
[有限背包題目](http://203.64.191.163/ShowProblem?problemid=a330)
有限背包和01背包很像,只是多遍歷一層物品數量
並為了優化複雜度將物品用2的次方分堆
分堆完後可以把他們當作是新的物品,那接下來就和01背包一樣了,決定要不要拿這些分好堆的物品。
假設現在有7個東西可以放
就分成1個東西一堆(a) 2個東西一堆(b) 4個東西一堆(c)
如果你要拿1個就是拿a,2個就是拿b,3個就是拿a+b,4個就是拿c,5個就是拿b+c.....
8個就1 2 4 1
這樣原本遍歷物品個數的複雜度就會降到log
#### 二維的
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define N 10001 //因為沒有壓空間,所以這裡不能開很大
int dp[101][N];
int main(){
int n, c;
cin >> n >> c;
for(int i = 0; i < n; i++){
int val, v, num;
cin >> val >> v >> num;
int minn = min(num, c/v);
for(int j = 0; j <= c; j++){//不選i
if(i > 0)dp[i][j] = dp[i-1][j];
}
for(int k = 1; minn > 0; k*=2){
if(k > minn){
k = minn;
}
minn-=k;
for(int j = c; j >= v*k; j--){
dp[i][j] = max(dp[i][j], dp[i][j-v*k] + val*k);
//注意不是用i-1 因為上一個狀態不是i-1,而是二進位拆分的上一組
//且因為是上一組,這裡一定要倒著dp,如果正著dp就會把上一組的蓋掉
}
}
}
cout << dp[n-1][c] << '\n';
}
```
#### 一維的
這題一維的反而比較簡單,因為不用多寫一個不選i的(因為只有一維,不用寫一個for把i-1轉移到i),而且因為二進位拆分的關係,本來二維DP就也要倒著
```cpp=
for(int i = 0; i < n;i++){
int val, v, num;
cin >> val >> v >> num;
int minn = min(num,c / v);
for(int k = 1; minn > 0; k *= 2){
if(k > minn)k = minn;
minn -= k;
for(int j = c; j >= v * k;j--){
if(j - v * k < 0)continue;
dp[j] = max(dp[j],dp[j - v * k] + val * k);
}
}
}
```
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define N 1000000
int dp[N+1];
int n,c;
int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
cin >> n >> c;
for(int i = 0; i < n;i++){
int val, v, num;
cin >> val >> v >> num;
int minn = min(num,c / v);
for(int k = 1; minn > 0; k *= 2){
if(k > minn)k = minn;
minn -= k;
for(int j = c; j >= v * k;j--){
if(j - v * k < 0)continue;
dp[j] = max(dp[j],dp[j - v * k] + val * k);
}
}
}
cout << dp[c] << '\n';
}
```
## 無限背包
[無限背包問題](http://203.64.191.163/ShowProblem?problemid=a331)
無限背包的概念也差不多
就是一個一個放放到不能放了
轉移式也差不多
但是無限背包一定要正著DP,因為可以無限拿,所以她的上一個狀態不是i-1,是上一個dp[i][j-v],dp[j-w] 已經包含了「前面這一輪」對同一件物品的更新結果。
如果用倒著DP的方法寫,就是強迫每個i只能拿一個(因為前面的東西其實是i-1層的)
```cpp=
for(int i = 0; i < n; i++){
int val, v;
cin >> val >> v;
for(int j = v; j <= c; j++){
dp[j] = max(dp[j], dp[j - v] + val);
}
}
```
```cpp=
#include<bits/stdc++.h>
using namespace std;
#define int long long
signed main(){
int n, c;
cin >> n >> c;
vector<int > dp(c+1,0);
int val, v;
cin >> val >> v;
for(int i = 1; i < c+1; i++){
if(i >= v)dp[i] = val;
}
for(int i = 1; i < n; i++){
cin >> val >> v;
for(int j = v; j <= c; j++){
dp[j] = max(dp[j], dp[j-v] + val);
}
}
cout << dp[c] << '\n';
}
```