多重背包问题的三种复杂度解法,O(n * w * c)、O(n*w*log c)和O(n * w)。

来源:互联网 发布:软件安装管理器 知乎 编辑:程序博客网 时间:2024/05/22 05:02

吹水:

初一的时候就遇到了要求快速解决多重背包问题的题目,当时没有总结的习惯,结果最近遇到的时候还有些懵,感觉基础不是很牢固,需要巩固一下,在这里写一下自己对题目中的两种做法的理解。

O(n * w *c)解法:

相信不用解释这个解法大家都懂,之所以列出来是为了作为后面的参考。

#define fo(i, x, y) for(int i = x; i <= y; i ++)#define fd(i, x, y) for(int i = x; i >= y; i --)#define max(a, b) ((a) > (b) ? (a) : (b))#define min(a, b) ((a) < (b) ? (a) : (b))using namespace std;const int Maxn = 1005, Maxm = 1005;int n, m, w[Maxn], v[Maxn], c[Maxn], f[Maxm + 1];int main() {    freopen("a.in", "r", stdin);    scanf("%d", &n);    fo(i, 1, n) scanf("%d %d %d", &w[i], &v[i], &c[i]);    fo(i, 1, n) {        fd(j, Maxm, w[i])            fo(k, 1, min(c[i], j / w[i]))                f[j] = max(f[j], f[j - k * w[i]] + k * v[i]);    }    scanf("%d", &m); printf("%d", f[m]);}

O(n*w*log c)解法:

先解释一下:n是物品个数,w是weight,即物体所占的份额,c就是物体可以有多少个。
对于当前的物品,若它能使用的个数为c,k是最大的正整数且满足2k+1<=ck=logc2-1),y = c(2k+11)
我们可以把原来可以用c次的物品拆分成k+2个只能用一次的物品。
前k+1个物品分别是:wi=w2i,vi=c2i,ci=1(0<=i<=k)
第k+2个物品是:w=wy,v=vy,c=1
转换成了01背包问题。
这样子,我们充分利用了二进制的原理,可以刚好表示出用了次数为0-c的原物品。

#include<cstdio>#define fo(i, x, y) for(int i = x; i <= y; i ++)#define fd(i, x, y) for(int i = x; i >= y; i --)#define max(a, b) ((a) > (b) ? (a) : (b))using namespace std;const int Maxn = 1005, Maxm = 1005;int n, m, a2[35], w[Maxn], v[Maxn], c[Maxn], f[Maxm + 1];int main() {    freopen("a.in", "r", stdin);    a2[0] = 1; fo(i, 1, 30) a2[i] = a2[i - 1] * 2;    scanf("%d", &n);    fo(i, 1, n) scanf("%d %d %d", &w[i], &v[i], &c[i]);    fo(i, 1, n) {        int k = log2(c[i]);        fo(z, 0, k)            fd(j, Maxm, a2[z] * w[i])                f[j] = max(f[j], f[j - a2[z] * w[i]] + a2[z] * v[i]);        int y = c[i] - a2[k];        fd(j, Maxm, y * w[i])            f[j] = max(f[j], f[j - y * w[i]] + y * v[i]);    }    scanf("%d", &m); printf("%d", f[m]);}

O(n * w)做法:

这已经是一个线性做法了,用了单调队列。
让我们观察一下暴力的dp式(为了方便,下面的除号全部默认下取整):
fi=max(fijw+jv)(1<=j<=min(c,j/w))
等价于fi=max(fk+(ik)/wv)(k=i(mod w)) and ((ik)/w<=c)
于是我们可以想到以(i % w)为关键字开w条单调队列辅助dp。
对于每一条队列,最方便的是存已占的份额(w)值,若队列中有两个x,y,且(x <y),则必须满足:
fx+(yx)/wv>=fy
且队列头start对于当前的i必须满足(istart)/w<=c
也许我们还需要一个滚动数组。

Code:

#include<cstdio>#define fo(i, x, y) for(int i = x; i <= y; i ++)#define max(a, b) ((a) > (b) ? (a) : (b))using namespace std;const int Maxn = 1000005, Maxm = 1000005;int n, m, o, w[Maxn], v[Maxn], c[Maxn], f[2][Maxm + 1];int d[Maxn];int main() {    scanf("%d", &n);    fo(i, 1, n) scanf("%d %d %d", &w[i], &v[i], &c[i]);    fo(i, 1, n) {        o = !o;        fo(mo, 0, w[i] - 1) {            int st = 1, en = 0;            fo(j, 0, max(0, (Maxm - mo) / w[i])) {                int k = mo + j * w[i];                while(st <= en && (k - d[st]) / w[i] > c[i]) st ++;                while(st <= en && f[!o][d[en]] + (k - d[en]) / w[i] * v[i] < f[!o][k]) en --;                d[++ en] = k;                if(st <= en) f[o][k] = f[!o][d[st]] + (k - d[st]) / w[i] * v[i]; else f[o][k] = 0;            }        }    }    scanf("%d", &m); printf("%d\n", f[o][m]);}

总结:

其实log算法在小数据中比队列算法还要快,因为常数小,所以比赛时看数据而定吧。

阅读全文
1 0
原创粉丝点击