POJ 1742 Coins(多重背包, 单调队列)
Description
You are to write a program which reads n,m,A1,A2,A3...An and C1,C2,C3...Cn corresponding to the number of Tony's coins of value A1,A2,A3...An then calculate how many prices(form 1 to m) Tony can pay use these coins.
Input
Output
Sample Input
3 10
1 2 4 2 1 1
2 5
1 4 2 1
0 0
Sample Output
8
4
多重背包, 可惜一般多重背包解法不可用
Q: 第二层循环到底是 v 还是余数 d ?
A: 严格分组背包问题的第二层循环是 d, 但也并非完全如此, 第二层分了3个部分, 分别是 01背包, 完全背包, 严格分组背包三种情况
思路:
1. 使用 DP 单调队列求解
2. 分析背包问题的一般解法, 并寻找优化方案
3. 背包问题一般解法的动态规划方程为 dp[i][v] = max(dp[i-1][v-k*w[i]]+k*v[i])
4. 将(3)写的再详细一点, 就是 dp[i][v] = max(dp[i-1][v](不拿), dp[i-1][v-w[i]]+v[i](拿一件), ... dp[i-1][v-k*w[i]]+k*v[i]), 假设 k 是允许拿的最多件数. 关于 k 的取值范围, 首先, k 应该小于 n[i](即第 i 件物品的件数), 其次, k*w[i] < v
5. 举个例子, 对于第 i 件物品, 假设 k == 2, 同时 v 恰好等于 6*w[i], 那么
dp[i][6*w[i]] = max(dp[i-1][6*w[i]], dp[i-1][5*w[i]]+v[i], dp[i-1][4*w[i]]+2*v[i])
dp[i][5*w[i]] = max(dp[i-1][5*w[i]], dp[i-1][4*w[i]]+v[i], dp[i-1][3*w[i]]+2*v[i])
dp[i][4*w[i]] = max(dp[i-1][4*w[i]], dp[i-1][3*w[i]]+v[i], dp[i-1][2*w[i]]+2*v[i])
观察上面三个式子, 发现等号右边有重复的部分, 比如 dp[i-1][4*w[i]] 在三个式子中都出现过, 那么对上式做一下调整
第一个式子, 右边都减去 6*v[i]
dp[i][6*w[i]] = max(dp[i-1][6*w[i]]-6*v[i], dp[i-1][5*w[i]]-5*v[i], dp[i-1][4*w[i]]-4*v[i]) + 6*v[i]
第二个式子, 等号右边减去 5*v[i]
dp[i][5*w[i]] = max(dp[i-1][5*w[i]]-5*v[i], dp[i-1][4*w[i]]-4*v[i], dp[i-1][3*w[i]]-3*v[i]) + 5*v[i]
第三个式子, 等号右边减去 4*v[i]
dp[i][4*w[i]] = max(dp[i-1][4*w[i]], dp[i-1][3*w[i]]-3*v[i], dp[i-1][2*w[i]]+2*v[i]) + 4*v[i]
经过转化, 三个式子右边就出现了部分相同的式子, 相同就意味着可以减少重复计算, 那么, 计算 dp[i][v] 的时候, 可以使用单调队列减少冗余计算, 比如
开始时, 队列含有 dp[i][4*w[i]] 等号右边三个子式, 求解完 dp[i][4*w[i]], 压缩唯一一个新的子式 dp[i-1][5*w[i]]-5*v[i], 并挤掉 dp[i-1][2*w[i]]+2*v[i], 最后压入 dp[i-1][6*w[i]]-6*v[i], 挤掉 dp[i-1][3*w[i]]-3*v[i], 单调队列能使这个过程的复杂度为 o(1) (dp[i-1][k*w[i]+d] 进入单调队列的次数仅有一次)
6. 再具体一些. v==k*w[i] 的意思是背包的容量恰好是第 i 件物品的 k 倍, 此时 d = v%w[i] = 0. 当 v == k*w[i]+1 时, 即 d == 1, 那么一次遍历可以求解 dp[i][6*w[i]+1], dp[5*w[i]+1], dp[i][4*w[i]]+1]... 可见, 每次遍历能够求解余数相同的那些数
假设 d == v%w[i], d 的取值范围是 [0, w[i]) , 每一项减去的是 v/w[i]
程序的框架可以是
7. 当 w == v 时的一个特例
每次入队(新加入队列)中的元素是 f[v]-(v/w[i])*v[i], 因为 w==v, 那么 f[v]-v+d, 其中 d=v%w[i]
返回的最大值是 队首元素+k/w[i]*v[i] = 队首元素+k-d
针对 1742 这道题, 题目仅要求求解能够覆盖的那些值, 所以题目变得简单一些了
对于 dp[i][k*w[i]+d], 我们仅需判断 dp[i][(k-(0...n[i]))*w[i]+d] 是否有 1 即可, 这有简化为 dp[i][(k-(0...n[i]))*w[i]+d] 的和是否为 0. 不为 0, 则覆盖
总结:
1. 多重背包的一般解法
<1> 直接解法. dp[i][v] = max(dp[i-1][v-k*w[i]]+k*v[i])
<2> 转换成01背包. 将一种物品拆分成 1, 2, 4, ...N-2^k-1件. 比如 13就能拆分成 1, 2, 4, 6 件, 然后使用 01 背包的思路求解
2. 单调队列的初始化方法
<1> st初始化为0, ed 初始化为 -1
<2> queue[++ed] = dp[v]
可以减少判断
3. 第 29 行代码 WA 过, v = d, 而不是 v =w[i]
代码:
#include <iostream>
using namespace std; const int MAXN = 150;
int w[MAXN], c[MAXN];
int n, m;
bool dp[100000+10], queue[100000+10]; int solve_dp() {
memset(dp, 0, sizeof(dp));
memset(queue, 0, sizeof(queue));
dp[0] = true; for(int i = 0; i < n; i ++) {
if(c[i] == 1) { // 仅允许一个包, 变成01背包问题
for(int v = m; v >= w[i]; v--) {
if(!dp[v] && dp[v-w[i]])
dp[v] = 1;
}
}else if(c[i]*w[i] >= m) { // 完全背包问题, 即 w[i]*c[i] < m, 放入件数的限制是 c[i]
for(int v = w[i]; v <= m; v++) {
if(!dp[v] && dp[v-w[i]])
dp[v] = 1;
}
}else{ // 严格的分组背包问题
for(int d = 0; d < w[i]; d++) { // 对于所有余数 d [0, w[i])
// 窗口大小为 c[i]
int sum = 0, st = 0, ed = -1; //st,ed 单调队列的开始和结尾, sum 队列中是否有一个 true
for(int v = d; v <= m; v+= w[i]) { // 完全背包 model, 但步长是 w[i]
if(ed - st == c[i]) { // 窗口大小为0, 移除队首元素, 队首后移一位
sum -= queue[st++];
}
queue[++ed] = dp[v];
sum += dp[v];
if(!dp[v] && sum)
dp[v] = 1;
}
}
}
}
int res = 0;
for(int i = 1; i <= m; i ++)
res += dp[i];
return res; }
int main() {
freopen("E:\\Copy\\ACM\\测试用例\\in.txt", "r", stdin);
while(cin >> n >> m && n != 0) {
for(int i = 0; i < n; i ++) {
scanf("%d", &w[i]);
}
for(int i = 0; i < n; i ++) {
scanf("%d", &c[i]);
}
// main function
cout << solve_dp() << endl;
}
return 0;
}
Update: 2014年3月14日10:04:41
1. sum = 1 -> dp[v] = 1 优化非常巧妙, 第二次做时依然没想到
2. 分组背包时, 注释写了完全背包 model, 但实际上写成 01 背包 model 也是可以的, 结果与之无关. 但写成 01 背包 model 更加合适, 毕竟分组背包的经典解法是转化为 01 背包
3. 此题和 九度 买卖股票 可以很好做下对比
4. 楼天成是男人就做八题其中一道
POJ 1742 Coins(多重背包, 单调队列)的更多相关文章
- POJ 1742 Coins (多重背包)
Coins Time Limit: 3000MS Memory Limit: 30000K Total Submissions: 28448 Accepted: 9645 Descriptio ...
- [BZOJ4182]Shopping (点分治+树上多重背包+单调队列优化)
[BZOJ4182]Shopping (点分治+树上多重背包+单调队列优化) 题面 马上就是小苗的生日了,为了给小苗准备礼物,小葱兴冲冲地来到了商店街.商店街有n个商店,并且它们之间的道路构成了一颗树 ...
- poj1742 Coins(多重背包+单调队列优化)
/* 这题卡常数.... 二进制优化或者单调队列会被卡 必须+上个特判才能过QAQ 单调队列维护之前的钱数有几个能拼出来的 循环的时候以钱数为步长 如果队列超过c[i]就说明队头的不能再用了 拿出来 ...
- 【POJ1276】Cash Machine(多重背包单调队列优化)
大神博客转载http://www.cppblog.com/MatoNo1/archive/2011/07/05/150231.aspx多重背包的单调队列初中就知道了但一直没(不会)写二进制优化初中就写 ...
- bzoj 1531 Bank notes 多重背包/单调队列
多重背包二进制优化终于写了一次,注意j的边界条件啊,疯狂RE(还是自己太菜了啊啊)最辣的辣鸡 #include<bits/stdc++.h> using namespace std; in ...
- BZOJ.4182.Shopping(点分治/dsu on tree 树形依赖背包 多重背包 单调队列)
BZOJ 题目的限制即:给定一棵树,只能任选一个连通块然后做背包,且每个点上的物品至少取一个.求花费为\(m\)时最大价值. 令\(f[i][j]\)表示在点\(i\),已用体积为\(j\)的最大价值 ...
- Luogu 3423 [POI 2005]BAN-银行票据 (多重背包单调队列优化 + 方案打印)
题意: 给出 n 种纸币的面值以及数量,求最少使用多少张纸币能凑成 M 的面额. 细节: 好像是要输出方案,看来很是头疼啊. 分析: 多重背包,裸体??? 咳咳,好吧需要低调,状态就出来了: dp [ ...
- 多重背包 /// 单调队列DP oj1943
题目大意: em.... 就是多重背包 挑战340页的东西 ...自己的笔记总结总是比较乱的 重点:原始的状态转移方程中 更新第 i 种物品时 重量%w[i] 的值不同 则它们之间是相互独立的: 1- ...
- hdu 2844 多重背包+单调队列优化
思路:把价值看做体积,而价值的大小还是其本身,那么只需判断1-m中的每个状态最大是否为自己,是就+1: #include<iostream> #include<algorithm&g ...
随机推荐
- 孙鑫VC++视频教程笔记
写在前面的话:在学习孙鑫老师的VC++视频时,为了加深自己对知识的深入理解,就做了下面的笔记. 第一讲: 第二讲: 第三讲: 第四讲: 第五讲: 第六讲: 第七讲: 第八讲: 第九讲: 第十讲: 第十 ...
- maven打包可以行文件,包含依赖包等
<build> <!-- 设定打包的名称 --> <finalName>ismp2xy</finalName> <plugins> < ...
- Log4php 使用心得
使用log4php 记录系统日志: 1.自动拦截php报出的错误,写日志 2.手动打印错误 set_error_handler('captureNormal',E_ERROR | E_PARSE); ...
- 高速掌握Lua 5.3 —— 扩展你的程序 (1)
Q:怎样在C中将Lua作为配置文件语言使用? A: "config.lua"文件里: -- window size width = 200 height = 300 "m ...
- Linux 下 ps 命令
简述 Linux中的ps命令是Process Status的缩写.ps命令用来列出系统中当前运行的那些进程.ps命令列出的是当前那些进程的快照,就是执行ps命令的那个时刻的那些进程,动态的显示进程信息 ...
- kubernetes daahboard权限限制
dashboard在多人使用的时候经常遇到误操作的情况,为了对dashboard进行限制,对dashboard进行了权限控制, 这里主要限制只允许pod被删除.1:创建对应权限的ClusterRole ...
- win7共享文件夹设置无密码
首先我们要启用guest账户,右键计算机 2 选择管理 3 选择本地用户和组 4 然后选择用户 5 然后选择Guest右键——属性——把账户已禁用勾掉,就可以了 6 然后点击桌面网络右键——属性 7 ...
- [LintCode]判断一个字符串是否包含另一个字符串的所有字符
问题描述: 比较两个字符串A和B,确定A中是否包含B中所有的字符.字符串A和B中的字符都是 大写字母. 样例 给出 A = "ABCD" B = "ACD",返 ...
- iOS边练边学--应用数据存储的常用方式(plist,Preference,NSKeyedArchiver)其中的三种
iOS应用数据存储的常用方式: XML属性列表(plist)归档 Preference(偏好设置) NSKeyedArchiver归档(NSCoding) SQLite3--这里暂且不讲 Core D ...
- iOS边练边学--UIScrollView和xib文件实现简单分页+定时器初使用
一.xib文件构成 二.自定义控件类(xib文件与自定义控件类的文件名字相同,并且将xib文件中父类控件的类名改成自定义控件类的名称) ***********自定义控件类需要的属性********** ...