多重背包问题的三种复杂度解法,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是最大的正整数且满足
我们可以把原来可以用c次的物品拆分成k+2个只能用一次的物品。
前k+1个物品分别是:
第k+2个物品是:
转换成了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式(为了方便,下面的除号全部默认下取整):
等价于
于是我们可以想到以(i % w)为关键字开w条单调队列辅助dp。
对于每一条队列,最方便的是存已占的份额(w)值,若队列中有两个x,y,且(x <y),则必须满足:
且队列头start对于当前的i必须满足
也许我们还需要一个滚动数组。
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算法在小数据中比队列算法还要快,因为常数小,所以比赛时看数据而定吧。
- 多重背包问题的三种复杂度解法,O(n * w * c)、O(n*w*log c)和O(n * w)。
- 2.8.2 Wi n d o w s定义的U n i c o d e数据类型
- 算法:动态规划之金矿问题(时间复杂度O(n*w))
- Wi n d o w s的内存结构
- 常用的Wi n d o w s消息
- 多重背包 O(W * sigma(logCi)) 算法
- hdu 1025&hdu 1025 LIS(O(n*n)和O(n*log(n)))两种解法
- Wi n E x e c和O p e n F i l e等,只是为了实现与1 6位Wi n d o w s程 序的向后兼容而存在
- Wi n d o w s函数l s t r c m p和l s t r c m p i是作为对Wi n d o w s函数 C o m p a r e S t r i n g的调用来实现
- 2.8.4 Wi n d o w s字符串函数
- Wi n d o w s支持两种类型的应用程序
- linux系统编程,常见系统函数【 o p e n、 r e a d、 w r i t e、 lseek 、c l o s e】
- 揭开A F N e t w o r k i n g 框 架 的神秘面纱 (上)
- 揭开A F N e t w o r k i n g 框 架 的神秘面纱(下)
- Erlang和C实现O(n)复杂度求中位数
- 约瑟夫环时间复杂度O(n)解法
- SkipList时间复杂度分析O(log n)
- [算法]Fibonacci数列O(n)和O(lgn)的解法
- pandas入门笔记
- DBMS体系介绍
- 触摸事件
- 检测手机最多可以几个点触摸
- plsql连接本地oracle和远程oracle
- 多重背包问题的三种复杂度解法,O(n * w * c)、O(n*w*log c)和O(n * w)。
- C# webBrowser写模拟器时的javascript脚本调用问题
- 欧拉通路
- htmlunit使用代理IP
- 后缀数组
- mybatis框架及原理
- CentOS7 安装 vsftpd 完整版
- (三)简单工厂模式详解
- ARM指令中如何判断一个立即数是有效立即数