寫這篇文章主要是爲了幫幫新人吧,dalao勿噴.qwq算法
每種物品都有一個價值w和體積c.//這個就是下面的變量名,請看清再往下看.數組
你如今有一個揹包容積爲V,你想用一些物品裝揹包使得物品總價值最大.函數
多種物品,每種物品只有一個.求能得到的最大總價值.post
咱們考慮是否選擇第i件物品時,是須要考慮前i-1件物品對答案的貢獻的.優化
若是咱們不選擇第i件物品,那咱們就至關因而用i-1件物品,填充了體積爲v的揹包所獲得的最優解.ui
而咱們選擇第i件物品的時候,咱們要獲得體積爲v的揹包,咱們須要經過填充用i-1件物品填充獲得的體積爲v-c[i]的揹包獲得體積爲v的揹包.spa
//請保證理解了上面加粗的字再往下看.code
因此根據上面的分析,咱們很容易設出01揹包的二維狀態blog
\(f[i][v]\)表明用i件物品填充爲體積爲v的揹包獲得的最大價值.隊列
從而很容易的寫出狀態轉移方程
\(f[i][v]=max(f[i-1][v],f[i-1][v-c[i]]+w[i])\)
對於當前第\(i\)件物品,咱們須要考慮其是否能讓咱們獲得更優解.
顯然,根據上面的話
咱們選擇第i件物品的時候,咱們要獲得體積爲v的揹包,咱們須要經過填充用i-1件物品填充獲得的體積爲v-c[i]的揹包獲得體積爲v的揹包.
咱們須要考慮到\(v-c[i]\)的狀況.
當不選當前第\(i\)件物品的時候,就對應了狀態轉移方程中的\(f[i-1][v]\),
而選擇的時候就對應了\(f[i-1][v-c[i]]+w[i]\).
咱們考慮一個問題.
若是一個體積爲5的物品價值爲10,而還有一個體積爲3的物品價值爲12,一個體積爲2的物品價值爲8.顯然咱們會選擇後者.
這樣咱們的狀態轉移方程中就不必定會選擇i物品。
其實最好地去理解揹包問題的話,仍是手跑一下這個過程,會加深理解。
代碼寫法↓
for(int i=1;i<=n;i++)//枚舉 物品 for(int j=1;j<=V;j++)//枚舉體積 if(j>=c[i]) f[i][j]=max(f[i-1][j],f[i-1][j-c[i]]+w[i]);//狀態轉移方程. else f[i][j]=f[i-1][j]. //上面的if語句是判斷當前容量的揹包可否被較小體積的揹包填充獲得. //顯然 若是j-c[i]<0咱們沒法填充 //(誰家揹包負的體積啊 (#`O′)
可是二維下的01揹包們仍是沒法知足,怎麼辦?
考慮一維如何寫!
仔細觀察會發現,二維狀態中,咱們的狀態每次都會傳遞給i(就是說咱們的前幾行會變得沒用.)
這就給了咱們寫一維dp的機會啊
因此咱們理所固然地設狀態\(f[i]\)表明體積爲i的時候所能獲得的最大價值.
容易發現的是,咱們的\(f[i]\)只會被i之前的狀態影響.
若是咱們順序枚舉,咱們的\(f[i]\)可能被前面的狀態影響.
因此咱們考慮倒敘枚舉,這樣咱們的\(f[i]\)不會被i之前的狀態影響,而咱們更新的話也不會影響其餘位置的狀態.
(能夠手繪一下這個過程,應該不是很難理解.)
或者來這裏看看(可能圖畫的有點醜了
代碼寫法↓
for(int i=1;i<=n;i++)//枚舉 物品 for(int j=V;j>=c[i];j--)//枚舉體積 f[j]=max(f[j],f[j-c[i]]+w[i]);//狀態轉移方程.
//應該不是很難理解.
01揹包問題是揹包問題中最基礎,也是最典型的問題.其狀態轉移方程也是基礎,更能夠演變成其餘揹包的問題.
請保證看懂以後再向下看.
例題-->p1048 採藥
此類揹包問題中,咱們的每種物品有無限多個,可重複選取.
相似於01揹包,咱們依舊須要考慮前i-1件物品的影響.
此時咱們依舊能夠設得二維狀態
\(f[i][v]\)表明用i件物品填充爲體積爲v的揹包獲得的最大價值
依舊很容易寫出狀態轉移方程
\(f[i][v]=max(f[i-1][v],f[i-1][j-k*c[i]]+k*w[i])\)
//其中k是咱們須要枚舉的物品件數.而咱們最多選取\(\left\lfloor\frac{V}{c[i]}\right\rfloor\)個(這個應該不用解釋
code
for(int i=1;i<=n;i++)//枚舉物品 for(int k=1;k<=V/c[i];k++)//咱們的物品最多隻能放件. for(int j=1;j<=t;j++) { if(c[i]*k<=j) f[i][j]=max(f[i-1][j],f[i-1][j-k*c[i]]+k*w[i]); else f[i][j]=f[i-1][j]; //判斷條件與01揹包相同. }
一樣地,咱們去考慮一維狀態(鬼才會考慮
依舊設
\(f[i]\)表明體積爲i的時候所能獲得的最大價值
與01揹包不一樣的是,咱們能夠重複選取同一件物品.
此時,咱們就須要考慮到前面i-1件物品中是否有已經選取過(其實不必
即,咱們當前選取的物品,可能以前已經選取過.咱們須要考慮以前物品對答案的貢獻.
所以咱們須要順序枚舉.
與01揹包一維的寫法相似.
代碼寫法↓
code
for(int i=1;i<=n;i++)//枚舉物品 for(int j=c[i];j<=V;j++)//枚舉體積.注意這裏是順序/ f[j]=max(f[j,f[j-c[i]]]+w[i]);//狀態轉移.
若是仍是不理解,來這裏看看.(就是上面那個鏈接)
徹底揹包也是相似於01揹包,應該也算上是它的一種變形.
比較通常的寫法是一維寫法,但願你們能掌握.
例題-->p1616 瘋狂的採藥
此類問題與前兩種揹包問題不一樣的是,
這裏的物品是有個數限制的.
(下面用\(num[i]\)表示物品i的個數.
咱們能夠枚舉物品個數,也能夠二進制拆分打包
一樣,咱們最多能夠放\(\left\lfloor\frac{V}{c[i]}\right\rfloor\),但咱們的物品數量可能不夠這麼多.
所以,咱們枚舉的物品個數是\(min(\left\lfloor\frac{V}{c[i]}\right\rfloor,num[i])\)
(其實沒這麼麻煩的,直接枚舉到\(num[i]\)便可)
多個物品,咱們就能夠當作爲一個大的物品,再去跑01揹包便可.
所以這個大物品的價值爲\(k\)×\(w[i]\),體積爲\(k\)×\(c[i]\)
code
for(int i=1;i<=n;i++)//枚舉物品 for(int j=V;j>=0;j--)//枚舉體積 for(int k=1;k<=num[i],k++) //這個枚舉到num[i]更省心 if(j-k*c[i]>=0)//判斷可否裝下. f[j]=max(f[j],f[j-k*c[i]]+k*w[i]);
其實還能夠對每種物品的個物品跑01揹包問題,效率特別低
這裏也給出代碼
code
for(int i=1;i<=n;i++) for(int k=1;k<=num[i];k++) for(int j=V;j>=c[i];j--) f[j]=max(f[j],f[j-c[i]]+w[i]);
可是此類問題,咱們的通常解法倒是
二進制拆分的原理
咱們能夠用 \(1,2,4,8...2^n\) 表示出\(1\) 到 \(2^{n+1}-1\)的全部數.
考慮咱們的二進制表示一個數。
根據等比數列求和,咱們很容易知道咱們獲得的數最大就是\(2^{n+1}-1\)
而咱們某一個數用二進制來表示的話,每一位上表明的數都是\(2\)的次冪.
就連奇數也能夠,例如->\(19\)能夠表示爲\(10010_{(2)}\)
這個原理的話應該很好理解,若是實在理解不了的話,仍是動手試一試,說服本身相信這一原理.
二進制拆分的作法
由於咱們的二進制表示法能夠表示從\(1\)到\(num[i]\)的全部數,咱們對其進行拆分,就獲得好多個大物品(這裏的大物品表明多個這樣的物品打包獲得的一個大物品).
(簡單來說,咱們能夠用一個大物品表明\(1,2,4,8..\)件物品的和。)
而這些大物品又能夠根據上面的原理表示出其餘不是2的次冪的物品的和.
所以這樣的作法是可行的.
咱們又獲得了多個大物品,因此再去跑01揹包便可.
這裏只給出拆分部分的代碼,相信你能夠碼出01揹包的代碼.
code
for(int i=1;i<=n;i++) { for(int j=1;j<=num[i];j<<=1) //二進制每一位枚舉. //注意要從小到大拆分 { num[i]-=j;//減去拆分出來的 new_c[++tot]=j*c[i];//合成一個大的物品的體積 new_w[tot]=j*w[i];//合成一個大的物品的價值 } if(num[i])//判斷是否會有餘下的部分. //就好像咱們某一件物品爲13,顯然拆成二進制爲1,2,4. //咱們餘出來的部分爲6,因此須要再來一份. { new_c[++tot]=num[i]*c[i]; new_w[tot]=num[i]*w[i]; num[i]=0; } }
時間複雜度分析
咱們拆分一種物品的時間複雜度爲\(log(num[i])\).
咱們總共會有n種物品,再配上枚舉體積的時間複雜度.
所以,二進制拆分作法的時間複雜度爲\(O(\sum_{i=1}^nlog(num[i])\)×\(V )\)
首先回想多重揹包最普通的狀態轉移方程
\(f[i][j]=max(f[i-1][j],f[i-1][j-k*c[i]]+k*w[i])\)
其中\(k \in [1,min(\left\lfloor\frac{V}{c[i]}\right\rfloor,num[i])]\)
下面用 \(lim\)表示\(min(\left\lfloor\frac{V}{c[i]}\right\rfloor,num[i])\)
容易發現的是\(f[i][j-k*c[i]]\)會被\(f[i][j-(k+1)*c[i]]\)影響 (很明顯吧
(咱們經過一件體積爲\(c[i]\)的物品填充體積爲\(j-(k+1)*c[i]\)的揹包,會獲得體積爲\(j-k*c[i]\)的揹包.)
概括來看的話
\(f[i][j]\)將會影響 \(f[i][j+k*c[i]]\) \((j+k*c[i]<=V)\)
\(c[i]=4\)
容易發現的是,同一顏色的格子,對\(c[i]\)取模獲得的餘數相同.
且,它們的差知足等差數列! (公差爲\(c[i]\).
通項公式爲 \(j=k*c[i]+\)取模獲得的餘數
因此咱們能夠根據對\(c[i]\)取模獲得的餘數進行分組.
便可分爲\(0,1,2,3{\dots}c[i]-1\) 共\(c[i]\)組
且每組之間的狀態轉移不互相影響.
(注意這裏是組.相同顏色爲一組
相同顏色的格子,位置靠後的格子,將受到位置靠前格子的影響.
//可是這樣的話,咱們的格子會重複受到影響.(這裏不打算深刻討論 懼怕誤人子弟
即\(f[9]\)可能受到\(f[5]\)的影響,也可能受到\(f[1]\)的影響
而\(f[5]\)也可能受到\(f[1]\)的影響.
因此咱們考慮將原始狀態轉移方程變形.
這裏一些推導過程我會寫的儘可能詳細(我也知道看不懂有多難受. qwq
令d=c[i],a=j/c[i],b=j%c[i]
其中a爲全選情況下的物品個數.
則\(j=a*d+b\)
則帶入原始的狀態轉移方程中
\(j-k*d = a*d+b-k*d\) $= (a-k)*d+b $
咱們令\((a-k)=k^{'}\)
再回想咱們最原始的狀態轉移方程中第二狀態 : \(f[i][j-k*c[i]]+k*w[i]\) 表明選擇\(k\)個當前\(i\)物品.
根據單步容斥 :全選\(-\)不選=選.
所以 \(a-(a-k)=k\)
而前面咱們已經令\((a-k)=k^{'}\)
而咱們要求的狀態也就變成了
\(f[i][j]=max(f[i-1][k^{'}*d+b]+a*w[i]-k^{'}*w[i])\)
而其中,咱們的\(a*w[i]\)爲一個常量(由於a已知.)
因此咱們的要求的狀態就變成了
\(f[i][j]=max(f[i-1][k^{'}*d+b]-k^{'}*w[i])+a*w[i]\)
根據咱們的
\(k \in [1,lim]\)
容易推知
\(k^{'} \in [a-k,a]\)
那麼
當前的\(f[i][j]\)求解的就是爲\(lim+1\)個數對應的\(f[i-1][k^{'}*d+b]-k^{'}*w[i]\)的最大值.
(之因此爲\(lim+1\)個數,是包括當前這個\(j\),還有前面的物品數量.)
將\(f[i][j]\)前面全部的\(f[i-1][k^{'}*d+b]-k^{'}*w[i]\)放入一個隊列.
那咱們的問題就是求這個最長爲\(lim+1\)的隊列的最大值+\(a*w[i]\).
所以咱們考慮到了單調隊列優化( ? ?ω?? )?
(這裏再也不對單調隊列多說.這個題的題解中,有很多講解此類算法的,若是不會的話仍是去看看再來看代碼.-->p1886 滑動窗口
//相信你只要仔細看了上面的推導過程,理解下面的代碼應該不是很難.
//可能不久的未來我會放一個加註釋的代碼(不是立flag.
//裏面兩個while應該是單調隊列的通常套路.
//這裏枚舉的\(k\)就是\(k^{'}\).
code
for(int i=1;i<=n;i++)//枚舉物品種類 { cin>>c[i]>>w[i]>>num[i];//c,w,num分別對應 體積,價值,個數 if(V/c[i] <num[i]) num[i]=V/c[i];//求lim for(int mo=0;mo<c[i];mo++)//枚舉餘數 { head=tail=0;//隊列初始化 for(int k=0;k<=(V-mo)/c[i];k++) { int x=k; int y=f[k*c[i]+mo]-k*w[i]; while(head<tail && que[head].pos<k-num)head++;//限制長度 while(head<tail && que[tail-1].value<=y)tail--; que[tail].value=y,que[tail].pos=x; tail++; f[k*c[i]+mo]=que[head].value+k*w[i]; //加上k*w[i]的緣由: //咱們的單調隊列維護的是前i-1種的狀態最大值. //所以這裏加上k*w[i]. } } }
這裏只簡單的進行一下分析.(其實我也不大會分析 qwq
咱們作一次單調隊列的時間複雜度爲\(O(n)\)
而對應的每次枚舉體積爲\(O(V)\)
所以總的時間複雜度爲\(O(n*V)\)
多重揹包的寫法通常爲二進制拆分.
單調隊列寫法有些超出noip範圍,但時間複雜度更優,能掌握仍是儘可能去掌握.
拆分物品這種思想應該不算很難理解,這個是比較通常的寫法.但願你們能掌握.
若是仍是比較抽象,但願你們能動筆嘗試一下.
例題-->p1776 寶物篩選(這個題以前仍是個pj/tg-,後來居然藍了 emmm
所謂的混合三種揹包就是存在三種物品。
一種物品有無數個(徹底揹包),一種物品有1個(01揹包),一種物品有\(num[i]\)個(多重揹包)
這個時候通常只須要判斷是哪種揹包便可,再對應地去選擇dp方程求解便可.
送上一波僞代碼
code
for(int i=1;i<=n;i++) { if(徹底揹包) { for(int j=c[i];j<=V;j++) f[j]=max(f[j],f[j-c[i]]+w[i]); } else if(01揹包) { for(int j=V;j>=c[i];j--) f[j]=max(f[j],f[j-c[i]]+w[i]); } else//不然就是徹底揹包了 { for(int j=V;j>=0;j--) for(int k=1;k<=num[i];k++) if(j-k*c[i]>=0) f[j]=max(f[j],f[j-k*c[i]]+k*w[i]); } }
//徹底揹包拆分的話,能夠當作01揹包來作.(提供思路
混合三種揹包問題應該不是很難理解(若是前面三種揹包你真正瞭解了的話.
結合起來考的話應該也不會不少.
(畢竟揹包問題太水了
例題:(這個我真的沒找到qwq
通常分組揹包問題中,每組中只能選擇一件物品.
狀態你們都會設\(f[k][v]\)表明前k組物品構成體積爲v的揹包所能取得的最大價值和.
狀態轉移方程也很容易想.
\(f[k][v]=max(f[k-1][v],f[k-1][v-c[i]]+w[i])\)
可是咱們每組物品中只能選擇一件物品.
//這個時候咱們就須要用到01揹包倒敘枚舉的思想.
code:
for(int i=1;i<=k;i++)//枚舉組別 for(int j=V;j>=0;j--)//枚舉體積 for(now=belong[i])//枚舉第i組的物品. { if(j-c[i]>=0) f[i][j]=max(f[i-1][j],f[i-1][j-c[now]]+w[now]); else f[i][j]=f[i-1][j]; }
這類問題是01揹包的演變,須要注意的位置就是咱們枚舉體積要在枚舉第i組的物品以前
(由於每組只能選一個!)
此類揹包問題中。若是咱們想選擇物品i的附件,那咱們必須選擇物品i.
在[Noip2006]金明的預算方案這題中.引入了主件與附件的關係.
就比如你買電扇必須先買電.
一個主件和它的附件集合實際上對應於分組揹包中的一個物品組.
每一個選擇了主件又選擇了若干附件的策略,對應這個物品組的中的一個物品.
(也就是說,咱們把'一個主件和它的附件集合'當作爲了一個能得到的最大價值的物品.)
具體實現呢?
咱們的主件有一些附件伴隨,咱們能夠選擇購買附件,也能夠不購買附件.
(固然咱們也能夠選擇不購買主件.
當咱們選擇一個主件的時候,咱們但願獲得的確定是最大價值.
如何作?
咱們能夠先對附件集合跑一遍01揹包,從而得到這一主件及其附件集合的最大的價值.
(或者是徹底揹包,視狀況而定.)
代碼大體寫法是這樣的↓
(每一個物品體積爲1,\(w[]\)表明價值.)
不敢保證正確性,不過通常都是這樣寫的qwq
for(int i=1;i<=n;i++)//枚舉主件. { memset(g,0,sizeof g);//作01揹包要初始化. for(now=belong[i])//枚舉第i件物品的附件. { for(int j=V-1;j>=c[now];j--)//由於要先選擇主件才能選擇附件,因此咱們從V-1開始. { g[j]=max(g[j],g[j-1]+w[now]); } } g[V]=g[V-1]+w[i]; for(int j=V;j>=0;j--) for(int k=1;k<=V;k++)//此時至關於"打包" .. { if(j-k>=0) f[j]=max(f[j],f[j-k]+w[i]+g[k-1]); } } printf("%d",f[V]);
有一種狀況,是主件的附件依舊有附件.(不會互相依賴.
對於這種依賴關係,咱們能夠構出這樣的圖.
這種揹包就是傳說中的樹形揹包.
(樹形dp的一種)(應該後面會有人講)或者等我講
這題就是一個典型的樹形揹包入門題
這類問題更是揹包問題的延伸,咱們須要考慮的就是如何取到每個主件及其附件的集合中的最大值.而這就運用到了前面01揹包.
例題-->p1064 金明的預算方案
前面兩種揹包問題,已經有了泛化物品的影子.
(哪裏有啊!喂,話說這是什麼鬼東西
先給出概念.
該類物品並無固定的體積和價值,而是它的價值隨着你分配給它的體積而變化
其實這個能夠抽象成一個函數圖象.
在定義域0~V中,此類物品的價值隨着分配給它的價值變化而變化.
(可能不是一次函數也不是二次函數.
畢竟我沒有遇到過這種題
如今應該很容易理解.
有依賴的揹包問題就有着這種泛化物品的思想.
若是對於某一個物品組,咱們分配給它的體積越大,顯然它的價值越大.
最終咱們的答案就是全部對答案有貢獻的物品組的和.(保證在限制範圍內.
這些物品組被分配體積的大小的限制就是0~V.
總的限制也是0~V,因此這就能夠抽象爲
最終結果\(f(V)=max(h(l)+g(V-l))\)
(這只是一個抽象的解釋,還須要具體問題具體分析.
隨着水平的提升(反正窩很弱QAQ
出題人會更加考察選手的思惟.(話說有這種毒瘤出題人嘛qwq
下面討論幾種變化.根據揹包九講的順序.
輸出方案
對於一個揹包問題,咱們已經獲得了最大價值.
如今良心毒瘤出題人要求你輸出選擇物品的方案
分析
咱們如今須要考慮的是如何記錄這個狀態.
很明顯記錄每一個狀態的最優值,是由狀態轉移方程的哪一項推出來的.
若是咱們知道了當前狀態是由哪個狀態推出來的,那咱們很容易的就能輸出方案.
開數組\(g[i][v]\)記錄狀態\(f[i][v]\)是由狀態轉移方程哪一項推出.
//以01揹包一維寫法爲例.
code
for(int i=1;i<=n;i++) { for(int j=V;j>=c[i];j--) { if(f[j]<f[j-c[i]]+w[i]) { f[j]=f[j-c[i]]+w[i]; g[i][j]=true;///選第i件物品 } else g[i][j]=false;///不選第i件物品 } }
輸出
code
int T=V; for(int i=n;i>=1;i--) { if(g[i][T]) { printf("used %d",i); T-=c[i];//減去物品i的體積. } }
不敢保證正確性,不過通常都是這樣寫的qwq
再放一下狀態轉移方程.
\(f[i][v]=max(f[i-1][v],f[i-1][v-c[i]]+w[i]\)
\(f[j]=max(f[j],f[j-c[i]]+w[i])\)
二維狀態能夠省去g數組,只須要判斷\(f[i][v]\)是等於\(f[i-1][v]\)仍是等於\(f[i-1][v-c[i]]+w[i]\)就能輸出方案.
一維狀態好像不能,我不會啊qwq
輸出字典序較小的最優方案
感受sort一下就能夠吧
根據原文敘述來看,是將物品逆序排列一下.
與上面輸出方案的解法相同(倒敘枚舉.
惟一須要判斷的是:
當\(f[i][v]==f[i-1][v]\) 而且\(f[i][v]==f[i-1][v-c[i]]+w[i]\)的時候.
咱們要選擇後面這個方案.由於這樣選擇的話,咱們會獲得更小的字典序.(很明顯吧
** 求次優解or第k優解 **
此類問題應該是比較難理解.
因此我會盡可能去詳細地解釋,qwq.
首先根據01揹包的遞推式:(這裏按照一維數組來說)
(v[i]表明物品i的體積,w[i]表明物品i的價值).
\(f(j)\)=\(max\left(f(j),f(j-v[i])+w[i]\right)\)
很容易發現\(f(j)\)的大小隻會與\(f(j)\)、\(f(j-v[i])+w[i]\)有關
咱們設\(f[i][k]\)表明體積爲i的時候,第k優解的值.
則從\(f[i][1]\)...\(f[i][k]\)必定是一個單調的序列.
\(f[i][1]\)即表明體積爲i的時候的最優解
很容易發現,咱們須要知道的是,可否經過使用某一物品填充其餘體積的揹包獲得當前體積下的更優解.
咱們用體積爲7價值爲10的物品填充成體積爲7的揹包,獲得的價值爲10. 而咱們發現又能夠經過一件體積爲3價值爲12的物品填充一個體積爲4價值爲6的揹包獲得價值爲18. 此時咱們體積爲7的揹包能取得的最優解爲18,次優解爲10. 咱們發現,這個體積爲4的揹包還有次優解4(它可能被兩個體積爲2的物品填充.) 此時咱們能夠經過這個次優解繼續更新體積爲7的揹包. 最終結果爲 18 16 10
所以咱們須要記錄一個變量c1表示體積爲j的時候的第c1優解可否被更新.
再去記錄一個變量c2表示體積爲j-v[i]的時候的第c2優解.
咱們能夠用v[i]去填充j-v[i]的揹包去獲得體積爲j的狀況,並得到價值w[i]. 同理j-v[i]也能夠被其餘物品填充而得到價值. 此時,若是咱們使用的填充物不一樣,咱們獲得的價值就不一樣.
這是一個刷表的過程(或者叫推表?
(這裏引用一句話)
一個正確的狀態轉移方程的求解過程遍歷了全部可用的策略,也就覆蓋了問題的全部方案。
考慮到咱們的最優解可能變化,變化成次優解.只用一個二維數組\(f[i][k]\)來實現可能會很困難.
因此咱們引入了一個新數組\(now[]\)來記錄當前狀態的第幾優解.
\(now[k]\)即表明當前體積爲i的時候的第k優解.
所以最後咱們能夠直接將\(now[]\)的值賦給\(f[i]\)數組
具體實現的話能夠看看個人這篇文章
例題的話也就是這個(上面的文章是這題的題解.裏面有詳細解釋.
主要參考-->dd大牛的《揹包九講》
強大的合夥人-->w_x_c_q
若是仍是有不懂的地方,但願能夠多多提問.
(畢竟我也不是個壞人,qwq)