多重背包问题的两种O(M*N)解法
多重背包的题目很多,最著名的是poj1742楼教主的男人八题之一。
poj1742:coins
有几种面值的钱币和每种的数量,问能够组成m以内的多少种钱数
这个题大家都归为多重背包问题,不过跟实际意义上的背包还是有所差别的
因为如果把钱币看作背包中的物品,那么这个物品的价值和重量是相等的。
也就是没有“性价比"的。。
一种比较快速简单的做法是:
在判断能否放满某个体积时,如果能放满,尽量少用当前物品,贪心一下,对当前物品最优即可。
也可以用dp的思路想,就是dp[i][j]保存 j 体积最少用多少个物品 i
状态转移很明显:如果 dp[i-1][j] 合法 那么显然 dp[i][j]=0,否则在判断dp[i][j]能否由dp[i][j-w[i]]+1得到
复杂度O(N*M),而且常数也很小,可以通过楼教主的题目
两种代码如下:
- #include <iostream>
- #include <stdio.h>
- #include<string.h>
- #include<algorithm>
- #include<string>
- #include<ctype.h>
- using namespace std;
- int num[];
- bool dp[];
- int a[];
- int c[];
- int n,m;
- int main()
- {
- freopen("in.txt","r",stdin);
- //int T,cas=0;
- //scanf("%d",&T);
- //while(T--)
- while(scanf("%d%d",&n,&m),n+m)
- {
- //cas++;
- //scanf("%d%d",&n,&m);
- for(int i=;i<=n;i++)
- scanf("%d",a+i);
- for(int i=;i<=n;i++)
- scanf("%d",c+i);
- memset(dp,,sizeof(dp));
- dp[]=;
- for(int i=;i<=n;i++)
- {
- memset(num,,sizeof(num));
- for(int j=a[i];j<=m;j++)
- {
- if(num[j-a[i]]>=c[i])
- continue;
- if(dp[j]||(!dp[j-a[i]]))
- continue;
- num[j]=num[j-a[i]]+;
- dp[j]=;
- }
- }
- int ans=;
- for(int i=;i<=m;i++)
- {
- ans+=dp[i];
- }
- //printf("Case %d: %d\n",cas,ans);
- printf("%d\n",ans);
- }
- return ;
- }
- #include <iostream>
- #include <stdio.h>
- #include<string.h>
- #include<algorithm>
- #include<string>
- #include<ctype.h>
- using namespace std;
- int dp[];
- int a[];
- int c[];
- int n,m;
- int main()
- {
- //freopen("in.txt","r",stdin);
- while(scanf("%d%d",&n,&m),n+m)
- {
- for(int i=;i<=n;i++)
- scanf("%d",a+i);
- for(int i=;i<=n;i++)
- scanf("%d",c+i);
- memset(dp,-,sizeof(dp));
- dp[]=;
- int ans=;
- for(int i=;i<=n;i++)
- {
- for(int j=;j<=m;j++)
- {
- if(dp[j]!=-)
- {
- dp[j]=;
- }
- else if(j>=a[i]&&dp[j-a[i]]!=-&&dp[j-a[i]]<c[i])
- {
- dp[j]=dp[j-a[i]]+;
- }
- }
- }
- for(int i=;i<=m;i++)
- {
- ans+=(dp[i]!=-);
- }
- printf("%d\n",ans);
- }
- return ;
- }
但是物品如果重量和价值不同,那么以上方法就不行了。。
以上方法的策略是对某一个价值,尽量少选当前物品,但是我们又知道背包问题中,我们要尽量使达到某一个价值时所用的重量最小
这两个策略显然是会出现矛盾的。
下面我们来看一下这种情况的解决方法:
先按照传统背包问题的写法,写出朴素dp方程:
dp[i][j]=max(dp[i-1][j-k*w[i]]+k*v[i]) 其中0<=k<=c[i]
显然这个方程的复杂度是 n*v*c 的。数据稍大就会超时,那么怎么优化呢
求最值的dp优化,首先想到是单调队列优化,但是这个方程貌似更单调队列无关
尝试把它变形
我们令 r=j%w[i] , p=j/w[i]
则以上方程变为
dp[i][ p*w[i]+r ]=dp[i-1][ (p-k)*w[i] + r]+k*v[i]。
为了观察方便,再令 k=p-k,并移项,得到
dp[i][ p*w[i]+ r]-p*v[i] =dp[i-1][k*w[i]+r]-k*v[i] 其中对于特定的p,k属于 [p-c[i] , p]。
这个式子就跟简单的单调队列优化dp的式子很像了。。
把k当作单调队列下标,对于[ 0,w[i]-1 ]的每一个r,枚举k,建立一个单调队列,就可以完成状态转移了
由于单调队列复杂度是O(n)
总复杂度即为 O(n*r*p)=O(n*m)
不过这个通用的做法由于常数过大。。我的程序没过楼教主的那道题,感人的TLE了一晚上
也不知道到底慢了多少
后来我百度了很多这道题单调队列的题解发现是用单调队列的写法去填bool数组。我感觉本质上和第一种方法没什么差别。所以就不纠结了= =
贴上第二种做法的核心代码:
- for(i=; i<=n; ++i)
- {
- int p=m/w[i];
- for(j=w[i]-; j+; --j)
- {
- s=e=;
- for(int k=p; k+; --k)
- {
- if(k*w[i]+j>m)
- continue
- node tmp;
- tmp.num=k;
- int tt=tmp.val=dp[k*w[i]+j]-k*v[i];
- int x;
- if(tt>=q[s].val)
- {
- s=e=;
- }
- else
- {
- for(x=e-; x>=s&&tt>=q[x].val; --x);
- e=x+;
- }
- q[e++]=tmp; //入队
- }
- //由于压缩到一维背包需要从大到小循环,故先将能转移到 k=p的所有状态入队
- for(int k=p; k+; --k)
- {
- if(k*w[i]+j>m)
- continue;
- node tmp;
- int x;
- if(k>=c[i])
- {
- tmp.num=k-c[i];
- int tt=tmp.val=dp[(k-c[i])*w[i]+j]-(k-c[i])*v[i];
- if(tt>=q[s].val)
- {
- s=e=;
- }
- else
- {
- for(x=e-; x-s+&&tt>=q[x].val; --x);
- e=x+;
- }
- q[e++]=tmp;
- } //入队
- for(x=s; e-x&&q[x].num>k; ++x);
- s=x; //出队
- dp[k*w[i]+j]=q[s].val+k*v[i];
- }
- }
- }
多重背包问题的两种O(M*N)解法的更多相关文章
- hdu2844 & poj1742 Coin ---多重背包--两种方法
意甲冠军:你有N种硬币,每个价格值A[i],每个号码C[i],要求. 在不超过M如果是,我们用这些硬币,有多少种付款的情况下,.那是,:1,2,3,4,5,....,M这么多的情况下,,你可以用你的硬 ...
- 多重背包问题:悼念512汶川大地震遇难同胞——珍惜现在,感恩生活(HDU 2191)(二进制优化)
悼念512汶川大地震遇难同胞——珍惜现在,感恩生活 HDU 2191 一道裸的多重背包问题: #include<iostream> #include<algorithm> #i ...
- 5. 多重背包问题 II 【用二进制优化】
多重背包问题 II 描述 有 NN 种物品和一个容量是 VV 的背包. 第 ii 种物品最多有 sisi 件,每件体积是 vivi,价值是 wiwi. 求解将哪些物品装入背包,可使物品体积总和不超过背 ...
- 4. 多重背包问题 I
多重背包问题 I 描述 有 NN 种物品和一个容量是 VV 的背包. 第 ii 种物品最多有 sisi 件,每件体积是 vivi,价值是 wiwi. 求解将哪些物品装入背包,可使物品体积总和不超过背包 ...
- C语言-多重背包问题
多重背包问题 问题:有N种物品和一个容量为V的背包.第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i].求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大. 分 ...
- O(V*n)的多重背包问题
多重背包问题: 有n件物品,第i件价值为wi,质量为vi,有c1件,问,给定容量V,求获得的最大价值. 朴素做法: 视为0,1,2,...,k种物品的分组背包 [每组只能选一个] f[i][j]=Ma ...
- 什么是CS和BS结构,两种结构的区别
什么是CS和BS结构,两种结构的区别 什么是C/S和B/S结构? C/S又称Client/Server或客户/服务器模式.服务器通常采用高性能的PC.工作站或小型机,并采用大型数据库系 ...
- I/O模型系列之四:两种高性能IO设计模式 Reactor 和 Proactor
不同的操作系统实现的io策略可能不一样,即使是同一个操作系统也可能存在多重io策略,常见如linux上的select,poll,epoll,面对这么多不同类型的io接口,这里需要一层抽象api来完成, ...
- POJ 3260 The Fewest Coins(多重背包问题, 找零问题, 二次DP)
Q: 既是多重背包, 还是找零问题, 怎么处理? A: 题意理解有误, 店主支付的硬币没有限制, 不占额度, 所以此题不比 1252 难多少 Description Farmer John has g ...
随机推荐
- brent ozar的sqlserver dba训练课程翻译——第二章:手动恢复数据库
备份的唯一原因 备份的唯一原因是我们可以还原 当我第一次成为sqlserver数据库管理员,只要备份工作都能成功运行,我就会觉得一切都很好.我会查看sqlserver代理,保证那些作业都在运行,然 ...
- JS浏览器对象-Location对象
1.返回web主机的域名 <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> ...
- Java正則表達式语法
Java正則表達式语法 字符 说明 \ 将下一字符标记为特殊字符.文本.反向引用或八进制转义符.比如,"n"匹配字符"n"."\n"匹配换行 ...
- C语言——文件
需要理解的知识点:数据流.缓冲区.文件类型.文件存取方式 C语言可以将相关定义的数据按照内存的原样写入文件,这对于大规模数据来说比较方便,因为文件的尺寸比单纯的ASCII存储要小很多. 一.文件 ...
- 常用渗透性测试工具(Tools for penetration testing)
常用渗透性测试工具 原文:http://hi.baidu.com/limpid/item/14a2df166adfa8cb38cb3068 对一个应用项目进行渗透性测试一般要经过三个步骤. 第一步, ...
- _getch() 和 getch() 及 _T()
带下划线_的函数一般是函数库内部的函数,而不带下划线的一般是提供给用户使用的函数.带下划线的目的是为了防止用户定义的函数和函数库的函数重名冲突,所以直接使用也是可以的.要用getch()必须引入头文件 ...
- c++11 : Local and Unnamed Types as Template Arguments
In N2402, Anthony Williams proposes that local types, and unnamed types be usable as template argume ...
- 关于IE6幽灵字体
前言:今天做项目的时候在IE6下出现了这样的一种现像,这种情况只在IE6下出现,最后在网友的帮助下这个问题最终得到了解决.所以马上作了下笔记! 情况如下图: 我在网上找了点资料出现IE6下幽灵字体的情 ...
- Java基础知识强化55:经典排序之归并排序(MergeSort)
1. 归并排序的原理: 原理,把原始数组分成若干子数组,对每一个子数组进行排序, 继续把子数组与子数组合并,合并后仍然有序,直到全部合并完,形成有序的数组 举例: 无序数组[6 2 4 1 5 9] ...
- js验证表单并提交
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...