多重揹包以及兩種優化方法

題目:

有 N 種物品和一個容量是 V 的揹包。

第 i 種物品最多有 si 件,每件體積是 vi,價值是 wi。

求解將哪些物品裝入揹包,可使物品體積總和不超過揹包容量,且價值總和最大。
輸出最大價值。

基本算法:

最樸素的算法是三層循環 第一層枚舉物品  第二層枚舉揹包容量 第三層枚舉拿k個i物品 那麼動態轉移方程爲

dp[j] = max(dp[j - w] + v, dp[j - 2 * w] + 2 * v, .... , dp[j - k * w] + k * v);

代碼如下:

/*

n件物品 w爲每件物品的體積 v爲價值 s爲物品總件數

*/

for (int i = 0; i < n; i++) {
    int w, v, s;
    cin >> w >> v >> s;
    for (int j = m; j >= 0; j--) {
        for (int k = 1; k <= s && k * w <= m; k++)
            dp[j] = max(dp[j], dp[j - k * w] + k * v);
    }
}

參考題目:https://www.acwing.com/problem/content/4/

 

二進制優化法:

對於多重揹包, 我們能通過拆分, 將重量爲w,價值爲v,一共有s份的物品拆成s份,每份的重量爲w, 價值爲v,從而轉化爲01揹包問題 但是一份一份的拆這是一種效率非常慢的做法  那麼我們就需要考慮一下如何用最少的數字能夠組成從1~ s的所有數  這個答案是log2(s), 向上取整;

舉個例子 例如s = 7 那麼構成1-7的所有數字我們最少需要log2(7)個數,也就是3個數字, 分別是1, 2, 4; 那麼10呢?1, 2, 4能組成最大值是7 那麼我們再加3就行了(有人會以爲是1, 2, 4, 8, 雖然也能組成1 - 10的所有數 但是這4個數能組成的最大數是15已經超出我們的需要了) 所以我們每次用s減去2^k 最後剩一個餘數  直接添加進去就行了

代碼如下:

struct Node {
    int v, w;
};

vector<Node> V;//最好使用vector 因爲你不容易便確定拆完後需要多少空間

for (int i = 0; i < n; i++) {
    int w, v, s;
    cin >> w >> v >> s;
    for (int j = 1; j <= s; j <<= 1) {
        s -= j;
        V.push_back({v * j, w * j});
    }
    if (s > 0) V.push_back({v * s, w * s});//有餘數就直接加進去
}

for (int i = 0; i < V.size(); i++) {
    for (int j = m; j >= V[i].w; j--)
        dp[j] = max(dp[j], dp[j - V[i].w] + V[i].v);
}

參考題目:https://www.acwing.com/problem/content/5/

 

單調隊列優化:

使用單調隊列優化需要先考慮兩點

1.對於每一種物品按照m % w(m爲揹包容積, w爲物品體積)的餘數分類

對於每一個物品都有餘數爲0, 1, ... w - 1 一共w類  而這每一類都相互獨立 換句話說 餘數爲0的這一類只能同過餘數爲0的轉移過來 其餘餘數同理 而所有的餘數加起來是全解

2.當我們考慮dp[j]時, dp[j] = max(dp[j - w] + v, dp[j - 2 * w] + 2 * v, .... , dp[j - k * w] + k * v);  而dp[j - w] + v, dp[j - 2 * w] + 2 * v, .... , dp[j - k * w] + k * v是在同一類餘數裏面連續的一串數 那麼我們考慮dp[j + v]時 其實將長度爲k的框整體後挪一位求這一連續區間的最大值  這就變成了經典的單調隊列問題

代碼如下:

/*

dp_pre記錄dp[i - 1][]
dp記錄dp[i][]
que[] 單調隊列

*/
for (int i = 0; i < n; i++) {
    int w, v, s;
    cin >> w >> v >> s;
    memcpy(dp_pre, dp, sizeof(dp));
    for (int i = 0; i < w; i++) {
        int l = 0, r = -1;
        for (int j = i; j <= m; j += w) {
            //j - s * w > que[l] 是判斷拿了多少物品 等價於(j - que[l]) / w > s
            //這就是單調隊列動態維護長度爲k的框的最大值 k實際上就是物品總數
            if (l <= r && j - s * w > que[l]) l++;
            if (l <= r) dp[j] = max(dp[j], dp_pre[que[l]] + (j - que[l]) / w * v);
            while (l <= r && dp_pre[que[r]] - (que[r] - i) / w * v <= dp_pre[j] - (j - i) / w * v) r--;
            que[++r] = j;
        }
    }
}

參考題目:https://www.acwing.com/problem/content/6/

 

參考視頻:https://www.bilibili.com/video/av33930433/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章