【多重揹包】二進制優化 && 單調隊列優化 && w == v 的特殊情況的處理

爲了彌補之前沒有好好學的揹包 = = 花了一下午來研究這個神奇的東西。。。

當時覺得揹包九講好多東西根本看不懂啊各種跪啊其實現在看來也還好啊 = =唉。。當初真的太水了

(PS:我依舊還是沒覺得noip2014D1T3是個揹包微笑


其實其他的都沒什麼好說的。。

01揹包和完全揹包就是枚舉順序問題 分組揹包和依賴揹包其實都可以看成是泛化物品 當然多重揹包其實也是……

不過多重揹包因爲複雜度可以優化所以有點研究價值。。。。


裸的多重揹包的複雜度是V * sigma(Mi)..就是N * M * V。。N = 100 M = 100 V = 1000 大概這樣?


嗯。。先考慮二進制優化。。。當時聽的時候一直不懂爲什麼每個數都能被拆成幾個2^i的和 其實特別顯然 = = 就是表示成二進制數啊!

對於一個體積爲v 價值爲w 數量爲v 的物品 分別對 { (2^i) * v, (2^i) * w } ( i = 1, 2, 3... k 且 2^k < M ) 做01揹包 最後再對剩餘的 M - 2^k 做一次01揹包。。  

那麼物體被分成了logM份。。所以複雜度變成了V * sigma(logMi) 。。大部分題應該都可以過了。。


void MultiplePack(int value,int weight,int num)  
{  
    if (value * num >= m) CompletePack(value, weight);  
    else  
    {  
        int k = 1;  
        while (k <= num)  
        {  
            ZeroOnePack(k * value,k * weight);  
            num -= k; k <<= 1;  
        }  
        ZeroOnePack(num * value, num * value);  
    }  
}  


然後是單調隊列優化。。。

首先多重揹包的遞推方程是這樣的 F[i][j] = max { F[i - 1] [j - k * v[i] ] + k * w[i] }  (0 <= k <= m[i])  

令d = v[i]  a = j / v[i]  b = j % v[i]  

F[i][j] = max { F[i - 1] [j - k * d ] + k * w[i] }  (0 <= k <= m[i])  

F[i][j] = max { F[i - 1] [a * d + b - k * d ] + k * w[i] }  (0 <= k <= m[i])  

F[i][j] = max { F[i - 1] [(a - k) * d + b] + k * w[i] }  (0 <= k <= m[i])  

令 h = a - k

F[i][j] = max { F[i - 1] [h * d + b] + (a - h) * w[i] }  (0 <= k <= m[i])  

F[i][j] = max { F[i - 1] [h * d + b] - h * w[i] } + a * w[i]  (0 <= k <= m[i])  


對於一個確定的i, j。a和b都是確定的 並且我們發現對於確定的i a和b的數量是不會超過v[i]的

那麼就把j按a或者b分成v[i]組 每組的轉移就是連續的 然後就可以單調隊列優化了O(∩_∩)O


這個代碼我也不知道是不是對的!碼了也沒交過題!僅供參考!

struct queue{
    int num[Nmax];
    int head, tail;
    
    inline void init() { head = 0; tail = -1; }
    inline void push_back(int a){ num[++tail] = a; }
    inline void pop_back() { --tail; }
    inline void pop_front() { ++head; }
    inline int front() { return num[head]; }
    inline int back() { return num[tail]; }
    inline int size() { return tail - head + 1; }
}q;

int N, V;
int w[Nmax], v[Nmax], m[Nmax];
int F[Nmax], f[Nmax];

inline void MultiPack()
{
    for (int i = 1; i <= N; ++i) {
        for (int j = 0; j < v[i]; ++j) {
            q.init();
            for (int k = j, i = 0; k <= V; k += v[i], ++i) {
                while (q.front() + m[i] < k) q.pop_front();
                F[k] = f[k] - i * w[i]; f[k] = F[q.front()] + i * w[i];
                while (F[q.back()] <= F[k]) q.pop_back();
                q.push_back(k);
            }
        }
    }
}


至於w == v的時候。。

我們可以直接把狀態改了 f[i][j]表示對於前i種物品剩餘j的空間 的第i種物品剩餘的最大值 = =

那麼轉移:

當f[i - 1][j] > 0時 f[i][j] = m[i] 其餘時候 f[i][j] = f[i][j - v[i]] - 1 (保證合法,不合法用-1表示就行了。。)

然後最後從小到大枚舉j就得到找到一個合法的f[N][j]就行了。。。


一道例題是 poj1742 Coins 

傳說中的男人八題之一啊= = 哦不我是妹子啊!!!

有n種硬幣 每種價值Ai 有Ci個 然後求總價值小於m的組合方案數

照上面的求就可以了。。。


#include <cstdio>
#include <iostream>
#include <cstring>

using namespace std;

const int Nmax = 105;
const int Mmax = 100005;

int N, M;
int A[Nmax], C[Nmax];
int f[Mmax];

int main()
{
    ios :: sync_with_stdio(false);
    while (cin >> N >> M && N) {
        for (int i = 1; i <= N; ++i) cin >> A[i];
        for (int i = 1; i <= N; ++i) cin >> C[i];
        
        memset(f, -1, sizeof(f)); f[0] = 0;
        for (int i = 1; i <= N; ++i) {
            for (int j = 0; j <= M; ++j) {
                if (f[j] >= 0) f[j] = C[i];
                else if(f[j - A[i]] == -1 || j < A[i]) f[j] = -1;
                else f[j] = f[j - A[i]] - 1;
            }
        }
        
        int ans = 0;
        for (int i = 1; i <= M; ++i) if (~f[i]) ++ans;
        printf("%d\n", ans);
    }
    return 0;
}


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