# 背包問題
## Knapsack
----
### [題目敘述](https://atcoder.jp/contests/dp/tasks/dp_d)
現在有 $N$ 個物品,
第 $i$ 個物品的重量為 $w_i$、價值為 $v_i$,
你有一個容量為 $W$ 的背包,
求背包內物品的最大價值總和
> $N<=100$ ,
> $w_i<=W<=10^5$ ,
> $v_i<=10^9$
----
### 想一想
----
#### 假設最後所選物品總重為 $x$ , $x$ 會有幾種可能?
----
每種物品可以取或不取,是 $2^n$ 種嗎 ?
其實只有 $W$ 種,想想看為什麼
----
#### 假設目前已經選了重量為 $y$ 的物品,
#### 再選一個重量會變為多少?
----
可能變為 $y+w_1$ 、 $y+w_2$ 、 $y+w_3$ $\ldots$ $y+w_n$
換句話說,一個重量為 $p$ 的狀態,
只能從 $p-w_1$ 、 $p-w_2$ 、 $p-w_3$ $\ldots$ $p-w_n$
等狀態中轉移過來
----
### 狀態怎麼訂?
如同之前所說的,狀態通常 $=$ 所求的答案,
於是乎,我們可以把 $dp[i]$ 定為,
所選物品重量總和為 $i$ 時的最大價值
----
### 怎麼轉?
$dp[0]=0$
$dp[i]=max(dp[i-w_k]+v_k)$
( $1<=k<=n$ , $w_k<=i$ )
----
### 非~常重要的實作
```cpp=
const int N=105,MaxW=100005;
int v[N],w[N];
int dp[MaxW];
void Knapsack(int n,int W){
for(int i=1;i<=n;i++){ // 遍歷每個物品
for(int j=W;j>0;j--){ // 從大到小遍歷重量
if(j>=w[i]) dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
}
```
#### 這段code有兩個重點
1. 遍歷重量是從大到小
2. 遍歷物品的迴圈在外
----
### 如果重量的迴圈是從小到大會怎麼樣
假設有個物品的重量是 $1$ ,
且價值是所有物品中最大的,
如果 $W>1$ 這個物品就會被取到兩次
----
### 如果遍歷重量的迴圈在外會怎麼樣
----
### 自己想 ~
---
## 背包問題們
----
### 常見的背包問題可以分為三種
1. 01背包
2. 無限背包
3. 有限背包
---
### [01背包](https://atcoder.jp/contests/dp/tasks/dp_e)
#### 每個物品只有一個,可以選擇取或不取
----
#### 剛剛給的例題就是01背包
(忘記的...自己[看著辦](https://atcoder.jp/contests/dp/tasks/dp_d)
---
### [無限背包](https://cses.fi/problemset/task/1633)
每個東西可以取無限次
----
### 怎麼做
感覺很複雜,其實只需稍微修改01背包的扣
----
### 想法
對於每個重量,最後一個選的,
可以是任何物品
----
### 程式~
```cpp=
const int N=105,MaxW=100005;
int v[N],w[N];
int dp[MaxW];
void Knapsack(int n,int W){
for(int i=1;i<=W;i++){ // 從小到大遍歷重量
for(int j=1;j<=n;j++){ // 遍歷每個物品
if(i>=w[j]) dp[i]=max(dp[i],dp[i-w[j]]+v[j]);
}
}
}
```
---
### [有限背包](https://www.luogu.com.cn/problem/P1776)
每個物品不一定只有一個,
可以當成有重複物品的01背包
----
#### 實作的部分
```cpp=
const int N=105,MaxW=100005;
int v[N],w[N],num[N]; // num是每個物品的數量
int dp[MaxW];
void Knapsack(int n,int W){
for(int i=1;i<=n;i++){ // 遍歷每個物品
for(int k=0;k<num[i];k++){ // 每個物品可以取num[i]次
for(int j=W;j>0;j--){ // 從大到小遍歷重量
if(j>=w[i]) dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}
}
}
```
----
### 神奇的優化
----
先說,接下來的內容看不懂是完全正常的
----
### 複雜度分析
原本的做法,複雜度為 $O(N\times W\times num)$
----
### 優化想法
真的有必要分成 $num$ 個物品嗎?
只要可以湊出 $1$ ~ $num$ 的這些數量就可以了吧
e.g. { $1,2,3$ } 就可以湊出 $6$ 以下的所有數字
----
### 那麼,要如何分塊呢?
----
### 二進位!
假設物品總數為 $x$ ,
將 $x$ 分為 { $2^1$ , $2^2$ , $2^3$ , $2^4$ , $2^p$ , $q$ }
$p$ 為使 $2^{p+1}-1<=x$ 的最大整數
$q=x-(2^{p+1}-1)$
----
### 醬一定可以嗎
假設有一個數量 $y$
1. 如果 $y<2^{p+1}$ ,就可以用那些 $2^k$ 的湊出來
2. 不然的話先扣掉 $q$ ,就會變成 1. 的情況了
----
### 再來看看複雜度
塊數由 $num$ 塊,變成最多 $\log_2(num)+1$
複雜度降為 $O(NW*log(num))$
----
[Sample Code](https://cses.fi/paste/580181f237cc6f6f276335/)
----
### 以下內容由PixelCat贊助播出
----
### PixelCat:
你有打算講 $O(NW)$ 的有限背包嗎
----
### 哪泥!??! 竟然可以 $O(NW)$
----
### 觀察一下
為了方便觀察,我們重新寫一個轉移式
$dp[i][j]=$
目前用了前 $i$ 種物品 , 重量為 $j$ 的最大價值
$dp[i][j]=max${ $dp[i-1][j-w[i]*k]$ } $+v[i]$
{ $0<=k<=num[i]$ }
----
可以發現,對於每個 $j$
只會取到與他差 $w[i]*k$ 的重量
假設他取的重量為 $x$
則有 $x\equiv j\pmod{w[i]}$
(這是同餘符號)
----
換句話說
### 我們只需要管同餘的那些重量
----
於是我們將同餘的數分在同一組
每次取出連續 $num[i]$ 格中最大值
可以維護一個[單調隊列](https://hackmd.io/@Paul-Liao/HkyhtXpAd#/)
----
[Sample Code](https://cses.fi/paste/feb80b9d9f7c56e22762ba/)
---
## 內容有點多
## 請大家小心食用