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. vue路由配置,vue子路由配置

    上一篇关于vue环境配置已经写好了!按照操作就行了! 现在一个项目已经部署完成,接下来我们从路由开始! 还记得在初始化项目的时候,有提示是否需要安装vue-router,对没错,vue中路由全靠它! ...

  2. java 多线程阻塞队列 与 阻塞方法与和非阻塞方法

    Queue是什么 队列,是一种数据结构.除了优先级队列和LIFO队列外,队列都是以FIFO(先进先出)的方式对各个元素进行排序的.无论使用哪种排序方式,队列的头都是调用remove()或poll()移 ...

  3. SharePoint自动化系列——Solution auto-redeploy using Selenium(C#)

    转载请注明出自天外归云的博客园:http://www.cnblogs.com/LanTianYou/ 本来的想法是做一个可以自动卸载并且部署新solution到SharePoint farm的tool ...

  4. RTX——第12章 系统时钟节拍和时间管理

    以下内容转载自安富莱电子: http://forum.armfly.com/forum.php 本章节为大家讲解 RTX 操作系统的时钟节拍和时间管理函数,其中时间管理函数是 RTX 的基本函数,初学 ...

  5. Java程序员应该了解的10个设计原则

    引用: http://www.cnblogs.com/leehongee/archive/2012/03/18/2404760.html 面向对象设计原则是OOPS(Object-Oriented P ...

  6. Java项目中如何扩展第三方jar包中的类?

    有些时候你对第三方得到jar包中的类并不是很满意,想根据实际情况做一些扩展.如果说第三方的jar包已经提供了一些可扩展的类,比如提供了Interceptor,Filter或者其他的类,那么使用原生的比 ...

  7. kubernetes master 高可用一键部署

    #地址见:https://github.com/SILLKY/kubernetes-pro/tree/master/Master-HA#包括其他一些文件,适当版本1.6.1#!/bin/bash ho ...

  8. kubernetes 阿里云安装(kubeadm方式)

    注意:不能修改hostnamecurl -sSL http://aliacs-k8s.oss-cn-hangzhou.aliyuncs.com/installer/kubemgr-1.6.1.sh & ...

  9. Android——子线程操作主线程

    子线程不能直接操作主线程 UI线程 //水平进度条 public void jdt1_onclick(View view) { final ProgressDialog pd = new Progre ...

  10. 内核定时器timer_list

    内核在时钟中断发生后执行检测各个定时器是否到期,到期后的定时器处理函数将作为软中断在底半部执行.实质上,时钟中断处理程序会唤起TIMER_SOFTIRQ软中断,运行当前处理器上到期的所有定时器.lin ...