背包九講筆記

https://blog.csdn.net/yandaoqiusheng/article/details/84782655

1. 0-1背包

N件體積為w[i]崔列,價值為v[i]的物品放入體積為V的背包中宰闰,求價值和的最大值彻犁。
開一個大小為V的數(shù)組械姻,問題轉(zhuǎn)化為求dp[V]
先對dp[i]初始化:

for (int j = 1; j <= V; j++) {
        if (w[1] <= j) dp[j] = v[1];///說明此時可以放進第一個物品
    }

再用滾動數(shù)組:

for (int i = 2; i <= N; i++) {
        for (int j = V; j >= 1; j--) {
            if (w[i] <= j) dp[j] = max(dp[j], dp[j - w[i]] + d[i]);
        }

可以精簡成:

for (int i = 1; i <= N; i++)//從1開始遍歷倦挂,省去了初始化邊界,下同
        for (int j = V; j >= w[i]; j--)
            dp[j] = max(dp[j], dp[j - w[i]] + d[i]);

關(guān)于初始化:
(1)若題目要求“恰好裝滿背包”担巩,將dp[0]設(shè)為0方援,其它設(shè)為-1。
(2)若沒有要求涛癌,則全部設(shè)為0犯戏。

2.完全背包

N種物品和一個容量為V的背包,每種物品都有無限件可用拳话。第i種物品的費用是w[i]先匪,價值是v[i]。求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量弃衍,且價值總和最大呀非。

類比0-1背包問題,顯然有狀態(tài)轉(zhuǎn)移方程:
\begin{equation} dp[i][j]=max(dp[i-1][j-k*w[i]]+k*v[i]) ,0<=k*w[i]<=j \tag{$1$} \end{equation}
只需要枚舉滿足條件的k即可镜盯,不停地取最大值即可岸裙。

優(yōu)化:

for (int i = 1; i <= N; i++)
    for (int j = w[i]; j <= V; j++)
        dp[j] = max(dp[j], dp[j - w[i]] + v[i]);

就是把0-1背包問題中的第二層循環(huán)由倒序遍歷改為順序遍歷,因為物品有無窮多個速缆,所以生成新的價值時要利用已經(jīng)更新的價值降允。
證明:
j-w[i]代替(1)中的j,得
dp[i][j-w[i]]=max(dp[i-1][j-(k+1)w[i]]+kv[i]),0<=kw[i]<=j-w[i]
對于固定的i艺糜,v[i]是定值剧董,可以提到最大值運算的外面,得
dp[i][j-w[i]]=max(dp[i-1][j-(k+1)w[i]]+(k+1)v[i])-v[i],w[i]<=(k+1)w[i]<=j
t=k+1倦踢,得
dp[i][j-w[i]]=max(dp[i-1][j-tw[i]]+tv[i])-v[i],w[i]<=tw[i]<=j
移項送滞,得
max(dp[i-1][j-tw[i]]+tv[i])=dp[i][j-w[i]]+v[i],w[i]<=tw[i]<=j
比較上式和(1)的右邊侠草,發(fā)現(xiàn)只差范圍中k=0的情況辱挥,所以
dp[i][j-w[i]]=max(dp[i][j-w[i]]+v[i],dp[i-1][j])
由此遞推式容易發(fā)現(xiàn),對于dp[i][j-w[i]]边涕,必須用到已經(jīng)更新的狀態(tài)晤碘,證明結(jié)束褂微。

3.多重背包

https://www.luogu.org/problem/P1776
N種物品和一個容量為maxw的背包。第i種物品最多有m[i]件可用园爷,每件體積是w[i]宠蚂,價值是v[i]。求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量童社,且價值總和最大求厕。
相當(dāng)于有界的完全背包問題。
同理完全背包問題扰楼,可以得到狀態(tài)轉(zhuǎn)移方程:
dp[i][j]=dp[i-1][j-k*w[i]]+k*v[i]
其中呀癣,k*w[i]<=j0<=k<=m[i]
由此可以得到代碼:

#include<iostream>
#include<algorithm>
using namespace std;
int n, maxw;
int dp[40010], v[100010], w[100010], m[100010];
int res;
int d, s, k;
int main() {
    cin >> n >> maxw;
    for (int i = 1; i <= n; i++) {
        cin >> v[i] >> w[i] >> m[i];
    }
    for (int i = 1; i <= maxw; i++)  
        dp[i] = (i / w[1] <= m[1]) ? (i / w[1]) * v[1] : m[1] * v[1];
    for (int i = 2; i <= n; i++) {
        for (int j = maxw; j >= 1; j--) {
            for (int k = 0; k <= m[i]; k++) {
                if (j < k * w[i]) break;
                dp[j] = max(dp[j], dp[j - k * w[i]] + k * v[i]);
            }
        }
    }
    cout << dp[maxw];
    return 0;
}

時間復(fù)雜度:O(maxw* \sum m[i])
由數(shù)據(jù)范圍弦赖,是10^9數(shù)量級项栏,會超時。
(1)單調(diào)隊列優(yōu)化:
首先變換狀態(tài)轉(zhuǎn)移方程:設(shè)sj除以w的商(向下取整)蹬竖,dj除以w的余數(shù)沼沈,令k=s-t,有:
dp[d+w*s]=max(dp[d+w*t]-v*t)+v*s,0<=s-t<=m
在此方程中币厕,sd都是定值列另,只有t是變量。
注意到旦装,dp[d+w*s]dp[d+w*(s-m)],dp[d+w*(s-m+1)],...,dp[d+w*(s-1)]轉(zhuǎn)移而來计盒,即dp[d+w*(s-1)]及其后面共m個狀態(tài)轉(zhuǎn)移到dp[d+w*s]蛤奥,而dp[d+w*(s-2)]及其后面共m個狀態(tài)轉(zhuǎn)移到dp[d+w*(s-1)]...即每次轉(zhuǎn)移都是m個連續(xù)狀態(tài)轉(zhuǎn)移到它們前面的那個狀態(tài)。
聯(lián)想到滑動窗口,這是一個連續(xù)區(qū)間的最值問題禾酱,可以用單調(diào)隊列!
算法:
邊讀入v,w,m邊做资厉,從0w-1枚舉d泄朴,對于每一個d,維護一個對dp[d+w*s]-v*s單減的隊列棒妨,并儲存每個元素的s值踪古。
d=0到d+w*k<=maxw進隊,保證隊首元素是目前遍歷的所有元素中最大的券腔。當(dāng)隊首元素的下標(biāo)和要進隊的元素的下標(biāo)差大于m時伏穆,隊首元素出隊。最后將要進隊元素對應(yīng)的dp值和現(xiàn)在的隊首元素取最大纷纫。

#include<iostream>
#include<algorithm>
using namespace std;
int n, maxw;
int v, w, m;
int ans;
struct T {
    int num;
    int val;
}q[100010];
int head, tail;
int dp[100010];
int now;
int main() {
    cin >> n >> maxw;
    for (int i = 1; i <= n; i++) {
        cin >> v >> w >> m;
        for (int d = 0; d < w; d++) {
            head = 1, tail = 0;
            for (int s = 0; d + s * w <= maxw; s++) {
                now = d + s * w;
                while (head <= tail && dp[now] - v * s >= q[tail].val) tail--;
                q[++tail].val = dp[now] - v * s;
                q[tail].num = s;
                if (s - q[head].num > m) head++;
                dp[now] = max(dp[now], q[head].val + v * s);
            }
        }
    }
    cout << dp[maxw];
    return 0;
}

幾點說明:
1.雖然看上去是三重循環(huán)枕扫,但是由于對d+w*t做了剩余類劃分,實際上只對maxw遍歷了一次辱魁,時間復(fù)雜度為O(n*maxw)烟瞧。
2.分清楚:dp存的是max(dp[d+w*t]-v*t)+v*s诗鸭,單調(diào)隊列存的是dp[d+w*t]-v*t,比較的時候要-v*s参滴,賦值的時候要+v*s强岸。
3.這種方法可以用于dp[j]=max(某些有規(guī)律的區(qū)域)+...

(2)狀態(tài)壓縮
把原問題轉(zhuǎn)化為有\sum m[i]個物品的0-1背包,時間復(fù)雜度仍是O(maxw*\sum m[i])砾赔,沒什么改進蝌箍,但是如果把第i種物品分成若干組,每組有1,2,4,...,2^{k-1},m[i]-2^{k}+1個物品暴心,容易證明0到m[i]的任何一個數(shù)都可以寫成若干組的和十绑。這種方法可以把時間復(fù)雜度降到O(maxw*\sum(log(m[i])))

#include<iostream>
#include<algorithm>
using namespace std;
int n, maxw;
int v, w, m;
int ans;
int num;
int dp[100010];
int main() {
    cin >> n >> maxw;
    for (int i = 1; i <= n; i++) {
        cin >> v >> w >> m;
        num = min(m, maxw / w);
        for (int k = 1; num; k <<= 1) {
            if (k > num) k = num;//注意不是num=k
            num -= k;
            for (int j = maxw; j >= w * k; j--) {
                dp[j] = max(dp[j], dp[j - w * k] + v * k);
            }
        }
    }
    cout << dp[maxw];
    return 0;
}

這個方法沒有第一種優(yōu)秀酷勺,但是好寫本橙,而且可以用于混合背包(0-1,完全脆诉,多重都有)甚亭,只要把完全背包的m設(shè)成maxw/w即可。
https://www.luogu.org/problem/P1833(最后一組測試數(shù)據(jù)有一個19:8)
注意:這種時間的讀寫击胜,用scanf("%d:%d %d:%d")亏狰,不要用cin.get()!

4.二維費用背包

對于每件物品偶摔,具有兩種不同的費用暇唾;選擇這件物品必須同時付出這兩種代價;對于每種代價都有一個可付出的最大值(背包容量)辰斋。問怎樣選擇物品可以得到最大的價值策州。設(shè)這兩種代價分別為代價1和代價2,第i件物品所需的兩種代價分別為w[i]g[i]宫仗。兩種代價可付出的最大值(兩種背包容量)分別為VT够挂。物品的價值為v[i]
和一維一樣藕夫,有狀態(tài)轉(zhuǎn)移方程:
f[i][j][k]=max(f[i-1][j][k],f[i-1][j-w[i]][k-g[i]]+v[i])孽糖。
代碼:

for (int i = 1; i <= n; i++)
    for (int j = V; j >= w[i]; j--)
        for (int k = T; k >= g[i]; k--)
            f[j][k] = max(f[j][k], f[j - w[i]][k - g[i]] + v[i]);

推論:所取物體的總個數(shù)不超過M的一維背包,可以看作另外一維費用是件數(shù)的二維背包毅贮,每個物品的件數(shù)費用都是1办悟,件數(shù)的最大值為M

5.分組背包

N件物品和一個容量為V的背包滩褥。第i件物品的費用是w[i]病蛉,價值是v[i]。這些物品被劃分為若干組,每組中的物品互相沖突铡恕,最多選一件。求解將哪些物品裝入背包可使這些物品的費用總和不超過背包容量丢间,且價值總和最大探熔。
設(shè)f[k][j]為前i組物品在背包容量為j的約束下的最大價值和,則:
f[i][j]=max(f[i-1][j],f[i-1][j-c[k]]+w[k](第k件物品屬于第i組))烘挫。
代碼:

    for (int i = 1; i <= n; i++)//遍歷所有的組
        for (int j = m; j >= 0; j--)
            for (int k = 1; k <= j; k++)//k<=j防止數(shù)組越界
                f[j] = max(f[j], f[j - k] + a[i][k]);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末诀艰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子饮六,更是在濱河造成了極大的恐慌其垄,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卤橄,死亡現(xiàn)場離奇詭異绿满,居然都是意外死亡,警方通過查閱死者的電腦和手機窟扑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門喇颁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嚎货,你說我怎么就攤上這事橘霎。” “怎么了殖属?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵姐叁,是天一觀的道長。 經(jīng)常有香客問我洗显,道長外潜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任挠唆,我火速辦了婚禮橡卤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘损搬。我一直安慰自己碧库,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布巧勤。 她就那樣靜靜地躺著嵌灰,像睡著了一般。 火紅的嫁衣襯著肌膚如雪颅悉。 梳的紋絲不亂的頭發(fā)上沽瞭,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天,我揣著相機與錄音剩瓶,去河邊找鬼驹溃。 笑死城丧,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的豌鹤。 我是一名探鬼主播亡哄,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼布疙!你這毒婦竟也來了蚊惯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤灵临,失蹤者是張志新(化名)和其女友劉穎截型,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體儒溉,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡宦焦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了顿涣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赶诊。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖园骆,靈堂內(nèi)的尸體忽然破棺而出舔痪,到底是詐尸還是另有隱情,我是刑警寧澤锌唾,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布锄码,位于F島的核電站,受9級特大地震影響晌涕,放射性物質(zhì)發(fā)生泄漏滋捶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一余黎、第九天 我趴在偏房一處隱蔽的房頂上張望重窟。 院中可真熱鬧,春花似錦惧财、人聲如沸巡扇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽厅翔。三九已至,卻和暖如春搀突,著一層夾襖步出監(jiān)牢的瞬間刀闷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留甸昏,地道東北人顽分。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像施蜜,于是被迫代替她去往敵國和親卒蘸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容