Time Limit: 1000MS | Memory Limit: 10000K | |
Total Submissions: 27570 | Accepted: 9806 |
Description
Input
Output
Sample Input
735 3 4 125 6 5 3 350 633 4 500 30 6 100 1 5 0 1 735 0 0 3 10 100 10 50 10 10
Sample Output
735 630 0 0
Hint
Source
取款机有n种面额的钱币,其面额为d[i],每种面额有最大可取用数num[i]
给一个所需要取用的总钱数needed,要求通过选择钱币的种类和数目,输出不大于这个数的最大的可构造钱数。
看完上面的题意描述很明显应该联想到多重背包,然后就用最朴素的多重背包写了,壕无疑问,T了。
所以后来开始考虑优化。
首先采用了二进制优化,将原本的num[i]个(需要做num[i]次选择)同一种物品,
重新变成重量(面额)分别为1d[i],2d[i],4d[i],8d[i]...的log2n个物品
照着背包九讲的模板敲的,竟然WA了,然后在网上找了一个ac的来对拍,找出了问题,
不乖背包九讲,错就错在自己没有好好理解好他的这一步二进制优化。
具体的错误,和测错数据,还有一些想法,都在下面代码的注释中,那一步错误改了就ac了。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
随后旋即用了单调队列优化,一开始自己实在是没有想到优化的切入点,第一次使用优化是在hdu 3401上的
那道题也是背包的变形,但是略微不同,他的第二下标的转移时连续的,而这里的确不是连续的,
首先看看递推式
hdu 3401: dp[i][j] = max (dp[i][j] , dp[i-1][j-k] + k*value[i])
本题: dp[i][j] = max (dp[i][j] , dp[i-1][j-k*d[i]] + k*d[i])
在那道题中,主需要不断在 forj 的循环中把最新求得的放入队列即可,但是这道题,如果按照那道题,
没求一个数就放一个进去,很显然是不行的,因为到时候取出来的第二下标不一定符合递推关系(上面标红处就是为难我的地方)
后来找了别人的代码,真的是学习了,先贴上来
#include
#include
using namespace std;
#define MAX 35
const int MAX_V = 100004;
//v、n、w:当前所处理的这类物品的体积、个数、价值
//V:背包体积, MAX_V:背包的体积上限值
//f[i]:体积为i的背包装前几种物品,能达到的价值上限。
int total;
inline void pack(int f[], int V, int v, int n, int w){
if (n == 0 || v == 0) return;
if (n == 1) { //01背包
for (int i = V; i >= v; --i)
if (f[i] < f[i - v] + w) {
f[i] = f[i - v] + w;
}
return;
}
if (n * v >= V - v + 1) { //完全背包(n >= V / v)
for (int i = v; i <= V; ++i)
if (f[i] < f[i - v] + w) {
f[i] = f[i - v] + w;
}
return;
}
int va[MAX_V], vb[MAX_V]; //va/vb: 主/辅助队列
for (int j = 0; j < v; ++j){ //多重背包
int *pb = va, *pe = va - 1; //pb/pe分别指向队列首/末元素
int *qb = vb, *qe = vb - 1; //qb/qe分别指向辅助队列首/末元素
for (int k = j, i = 0; k <= V; k += v, ++i) {
if (pe == pb + n){ //若队列大小达到指定值,第一个元素X出队。
if (*pb == *qb) ++qb; //若辅助队列第一个元素等于X,该元素也出队。
++pb;
}
int tt = f[k] - i * w;
*++pe = tt; //元素X进队
//删除辅助队列所有小于X的元素,qb到qe单调递减,也可以用二分法
while (qe >= qb && *qe < tt) --qe;
*++qe = tt; //元素X也存放入辅助队列
f[k] = *qb + i * w; //辅助队列首元素恒为指定队列所有元素的最大值
//total = f[k];
}
}
}
int main()
{
int V;
while(~scanf("%d",&V)){
int v[100010] = {0};
int w[100010] = {0};
int f[100010] = {0};
int k;
scanf("%d",&k);
if(k == 0){
cout<<"0"<=0 ; --j){//最大值f[V]开始搜索等于或小于限度的最大值
if(f[j] <= V){
cout<
这这这实在是没想到啊............................姿势不够,还需努力
简单讲一下,再看一下递推式:
dp[i][j] = max (dp[i][j] , dp[i-1][j-k*d[i]] + k*d[i])
经过这题我发现一个做单调队列优化的小技巧,不要有太多的执念在数字的形式上,
最主要的是看等号左边和右边关键值的差
原来看着这个式子什么思路都没有,但是后来发现,
(1)左边的第二下标和右边的第二下标之差为k*d[i]。
(2)又总是可以找到某些a,b,c,使得左边的j=b+a*d[i](b
(3)将两式相减那么第二下标之差,同时右边dp多加的那个数,就有 k*d[i]=(a-c)*d[i]
那么在我的代码里面呢,那个a和c就分别是不同时刻的k,这样就能将向前求取最大值的过程成功构造出来了,也就是说,化归到只与一个变量有关
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include