Description

People in Silverland use coins.They have coins of value A1,A2,A3...An Silverland dollar.One day Tony opened his money-box and found there were some coins.He decided to buy a very nice watch in a nearby shop. He wanted to pay the exact price(without change) and he known the price would not more than m.But he didn't know the exact price of the watch. 
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

The input contains several test cases. The first line of each test case contains two integers n(1<=n<=100),m(m<=100000).The second line contains 2n integers, denoting A1,A2,A3...An,C1,C2,C3...Cn (1<=Ai<=100000,1<=Ci<=1000). The last test case is followed by two zeros.

Output

For each test case output the answer on a single line.

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(多重背包, 单调队列)的更多相关文章

  1. POJ 1742 Coins (多重背包)

    Coins Time Limit: 3000MS   Memory Limit: 30000K Total Submissions: 28448   Accepted: 9645 Descriptio ...

  2. [BZOJ4182]Shopping (点分治+树上多重背包+单调队列优化)

    [BZOJ4182]Shopping (点分治+树上多重背包+单调队列优化) 题面 马上就是小苗的生日了,为了给小苗准备礼物,小葱兴冲冲地来到了商店街.商店街有n个商店,并且它们之间的道路构成了一颗树 ...

  3. poj1742 Coins(多重背包+单调队列优化)

    /* 这题卡常数.... 二进制优化或者单调队列会被卡 必须+上个特判才能过QAQ 单调队列维护之前的钱数有几个能拼出来的 循环的时候以钱数为步长 如果队列超过c[i]就说明队头的不能再用了 拿出来 ...

  4. 【POJ1276】Cash Machine(多重背包单调队列优化)

    大神博客转载http://www.cppblog.com/MatoNo1/archive/2011/07/05/150231.aspx多重背包的单调队列初中就知道了但一直没(不会)写二进制优化初中就写 ...

  5. bzoj 1531 Bank notes 多重背包/单调队列

    多重背包二进制优化终于写了一次,注意j的边界条件啊,疯狂RE(还是自己太菜了啊啊)最辣的辣鸡 #include<bits/stdc++.h> using namespace std; in ...

  6. BZOJ.4182.Shopping(点分治/dsu on tree 树形依赖背包 多重背包 单调队列)

    BZOJ 题目的限制即:给定一棵树,只能任选一个连通块然后做背包,且每个点上的物品至少取一个.求花费为\(m\)时最大价值. 令\(f[i][j]\)表示在点\(i\),已用体积为\(j\)的最大价值 ...

  7. Luogu 3423 [POI 2005]BAN-银行票据 (多重背包单调队列优化 + 方案打印)

    题意: 给出 n 种纸币的面值以及数量,求最少使用多少张纸币能凑成 M 的面额. 细节: 好像是要输出方案,看来很是头疼啊. 分析: 多重背包,裸体??? 咳咳,好吧需要低调,状态就出来了: dp [ ...

  8. 多重背包 /// 单调队列DP oj1943

    题目大意: em.... 就是多重背包 挑战340页的东西 ...自己的笔记总结总是比较乱的 重点:原始的状态转移方程中 更新第 i 种物品时 重量%w[i] 的值不同 则它们之间是相互独立的: 1- ...

  9. hdu 2844 多重背包+单调队列优化

    思路:把价值看做体积,而价值的大小还是其本身,那么只需判断1-m中的每个状态最大是否为自己,是就+1: #include<iostream> #include<algorithm&g ...

随机推荐

  1. Dubbo阅读笔记——高级功能

    事件处理线程说明 如果事件处理的逻辑能迅速完成,并且不会发起新的IO请求,比如只是在内存中记个标识,则直接在IO线程上处理更快,因为减少了线程池调度. 但如果事件处理逻辑较慢,或者需要发起新的IO请求 ...

  2. 《CCNA(640-802)全套课程讲义》笔记

    路由器: CLI(命令行界面)形式相对应的是GUI  (图形用户界面) DTE:数据终端设备,指的是位于用户网络接口的用户端设备,通常情况下,路由器端为DTE端. DCE:数据通讯设备,为通信提供时钟 ...

  3. Python的sys.argv使用说明 通过终端写入环境变量

    刚开始使用这个参数的时候,很不明白其含义.网上搜索很多都是贴的官网上面的一则实例,说看懂,就明白.可是,我看不懂.现在在回头看这个参数使用,并不是很麻烦. 举几个小例子就明白了. 创建一个脚本,内容如 ...

  4. 获取Django中model字段名 字段的verbose_name

    obj._meta.fields 为关键 obj为model类 推荐使用函数 from django.apps import apps def getmodelfield(appname,modeln ...

  5. 重启sphinx

    connection to localhost:9312 failed 只能上传不能下载 /usr/sbin/setenforce 0 立刻关闭 SELINUX/usr/sbin/setenforce ...

  6. 一款基于jquery的手风琴显示详情

    今天要各网友分享一款基于jquery的手风琴显示详情实例.当单击顶部箭头的时候,该项以手风琴的形式展示显示详情.效果图如下: 在线预览   源码下载 实现的代码. html代码: <div al ...

  7. 【火狐FireFox】同步失败后,书签被覆盖,如何恢复书签

    问题场景: 使用公司的电脑,下载安装火狐,登录个人帐号后,火狐会自动开始同步书签.但有时候会同步失败,比如登录之前选的是[本地服务],而最新的书签都是在[全球服务]理,那么很有可能同步到的是N久之前的 ...

  8. win10无法访问局域网共享文件?(因微软账户和本地账户登陆问题导致)

    1 笔记本系统win10 X64企业版,其中一文件夹已设置为“共享”.本地帐号登录系统. 2 平板电脑系统win8.1 X64专业版,可以顺畅的访问笔记本的共享文件.微软帐号登录系统. 3 平板电脑系 ...

  9. git中文乱码解决方案

    解决方案: 在bash提示符下输入: git config --global core.quotepath false core.quotepath设为false的话,就不会对0x80以上的字符进行q ...

  10. ip辅助和别名的区别

    更流畅 IP 别名和辅助 IP 地址 2017-01-25 12:05             838人阅读             评论(0)             收藏              ...