背包問題入門(單調隊列優化多重背包


背包問題

寫這篇文章主要是為了幫幫新人吧,dalao勿噴.qwq

一般的背包問題問法

 每種物品都有一個價值w和體積c.//這個就是下面的變量名,請看清再往下看.

 你現在有一個背包容積為V,你想用一些物品裝背包使得物品總價值最大.

01背包

  多種物品,每種物品只有一個.求能獲得的最大總價值.

 我們考慮是否選擇第i件物品時,是需要考慮前i-1件物品對答案的貢獻的.

分析

 如果我們不選擇第i件物品,那我們就相當於是用i-1件物品,填充了體積為v的背包所得到的最優解.

 而我們選擇第i件物品的時候,我們要得到體積為v的背包,我們需要通過填充用i-1件物品填充得到的體積為v-c[i]的背包得到體積為v的背包.

//請保證理解了上面加粗的字再往下看.

所以根據上面的分析,我們很容易設出01背包的二維狀態

\(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]\).

Q:是不是在狀態轉移方程中一定會選擇當前i物品?

A:不會

我們考慮一個問題.

如果一個體積為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)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM