多重背包的优化

多重背包:有\(n\)种物品与承重为\(V\)的背包。每种物品有有限件\(s_i\),每个物品都有对应的重量\(v_i\)与价值\(c_i\),求最大能装的价值。

暴力做法 \(O(nVs)\)

for(int i=1; i<=n; i++) {
    for(int j=V; j>=0; j--) {
        for(int k=0; k<=s[i]; k++) {
            if(j-v[i]*k < 0) break;
            f[j] = max(f[j], f[j-v[i]*k] + c[i]*k);
        }
    }
}

二进制分组优化 \(O(nV log s)\)

这个优化是把第三个循环优化。原来是枚举\(0\)~\(s_i\)。可以对\(s_i\)拆分

\(s_i\) \(=\) \(12\), 分解成\(1+2+4+5\),这样所有\(1\)~\(12\)的数都可以用它们中某些数的和表示.因此只要循环4次就能得到正确答案

具体拆分方法是从\(2^0\)开始,\(2^1\)\(2^2\),...最后剩下的无法拆的单独算一个.

单调队列优化 \(O(nV)\)

\(d = v_i\), \(a = j / v_i\), \(b =\) \(j \mod v_i\)

实际上f[j]可以由以下这些状态转移来:

f[y] (\(y=b\), \(b+d\), \(b+2d\), \(b+3d\), \(b+4d\),.., \(j\))

那么f[j] = max(f[b+k*d] - k*c[i]) + a*c[i];

当前问题就是求前面\(j\)\(y\)中的前面的\(s_i+1\)个数的最大值

第一个状态序列存储可用状态

第二个单调队列可以维护状态队列的最值,查询\(O(1)\)

两种优化的代码

Luogu P1833 樱花

题目描述

爱与愁大神后院里种了n棵樱花树,每棵都有美学值Ci。爱与愁大神在每天上学前都会来赏花。爱与愁大神可是生物学霸,他懂得如何欣赏樱花:一种樱花树看一遍过,一种樱花树最多看Ai遍,一种樱花树可以看无数遍。但是看每棵樱花树都有一定的时间Ti。爱与愁大神离去上学的时间只剩下一小会儿了。求解看哪几棵樱花树能使美学值最高且爱与愁大神能准时(或提早)去上学。

输入输出格式

输入格式:

共n+1行:

第1行:三个数:现在时间Ts(几点:几分),去上学的时间Te(几点:几分),爱与愁大神院子里有几棵樱花树n。

第2行~第n+1行:每行三个数:看完第i棵树的耗费时间Ti,第i棵树的美学值Ci,看第i棵树的次数Pi(Pi=0表示无数次,Pi是其他数字表示最多可看的次数Pi)。

输出格式:

只有一个整数,表示最大美学值。

输入输出样例

输入样例#1:

6:50 7:00 3
2 1 0
3 3 1
4 5 4

输出样例#1:

11

说明

100%数据:Te-Ts ≤ 1000,n ≤ 10000

样例解释:赏第一棵樱花树一次,赏第三棵樱花树2次

题解

(当时做这都题的时候不优化的暴力DP是过不了的,现在貌似又放宽了时限)

二进制优化版:

#include <iostream>
#include <cstdio>
using namespace std;

#define MAXN 10100

int V, n;
int v[MAXN], c[MAXN], s[MAXN], f[MAXN];

int main() {
    int a1, b1, c1, d1;
    scanf("%d:%d %d:%d %d", &a1, &b1, &c1, &d1, &n);
    V = (c1 - a1) * 60 + d1 - b1;
    for(int i=1; i<=n; i++) {
        scanf("%d%d%d", &v[i], &c[i], &s[i]);
        if(!s[i]) {
            for(int j=v[i]; j<=V; j++)
                f[j] = max(f[j], f[j-v[i]] + c[i]);
        } else {
            int k, m;
            for(k=1; (k<<1)<s[i]; k<<=1) {
                for(int j=V; j>=v[i]*k; j--)
                    f[j] = max(f[j], f[j-v[i]*k] + c[i]*k);
            }
            m = s[i] - k + 1;
            for(int j=V; j>=v[i]*m; j--)
                f[j] = max(f[j], f[j-v[i]*m] + c[i]*m);
        }
    }
    printf("%d\n", f[V]);
    return 0;
}

单调队列优化版:

#include <iostream>
#include <cstdio>
using namespace std;

#define MAXN 10010

int V, n;
int v[MAXN], c[MAXN], s[MAXN], f[MAXN / 10];
int Q1[MAXN], Q2[MAXN]; //合法状态queue 与单调queue 

int main() {
    int a1, b1, c1, d1;
    scanf("%d:%d %d:%d %d", &a1, &b1, &c1, &d1, &n);
    V = (c1 - a1) * 60 + d1 - b1;
    int Head1, Head2, Tail1, Tail2, Cnt, t; //queue相关 
    for(int i=1; i<=n; i++) {
        scanf("%d%d%d", &v[i], &c[i], &s[i]);
        if(!s[i]) { //先跑完全背包
            for(int j=v[i]; j<=V; j++)
                f[j] = max(f[j], f[j-v[i]] + c[i]);
        }
    }
    for(int i=1; i<=n; i++) { //单调队列优化的多重背包
        if(!s[i]) continue;
        s[i] = min(s[i], V / v[i]);
        for(int j = 0; j < v[i]; j++) {
            Head1 = Tail1 = Head2 = Tail2 = Cnt = 0;
            for(int k = j; k <= V; k += v[i]) {
                if(Tail1 - Head1 == s[i] + 1) {
                    if(Q2[Head2 + 1] == Q1[Head1 + 1]) ++ Head2;
                    ++ Head1;
                }
                t = f[k] - Cnt * c[i];
                Q1[++ Tail1] = t;
                while(Head2 < Tail2 && Q2[Tail2] < t) -- Tail2;
                Q2[++ Tail2] = t;
                f[k] = Q2[Head2 + 1] + Cnt * c[i];
                ++ Cnt;
            }
        }
    }
    printf("%d\n", f[V]);
    return 0;
} 

猜你喜欢

转载自www.cnblogs.com/cute-hzy/p/9326230.html