【HDU】2191悼念512汶川大地震遇难同胞(多重背包)(单调队列)

【HDU】2191悼念512汶川大地震遇难同胞(多重背包)(单调队列)

Time Limit: 1000/1000 MS (Java/Others)
Memory Limit: 32768/32768 K (Java/Others)

Problem Description

急!灾区的食物依然短缺!
为了挽救灾区同胞的生命,心系灾区同胞的你准备自己采购一些粮食支援灾区,现在假设你一共有资金n元,而市场有m种大米,每种大米都是袋装产品,其价格不等,并且只能整袋购买。
请问:你用有限的资金最多能采购多少公斤粮食呢?

Input

输入数据首先包含一个正整数C,表示有C组测试用例,每组测试用例的第一行是两个整数n和m(1<=n<=100, 1<=m<=100),分别表示经费的金额和大米的种类,然后是m行数据,每行包含3个数p,h和c(1<=p<=20,1<=h<=200,1<=c<=20),分别表示每袋的价格、每袋的重量以及对应种类大米的袋数。

Output

对于每组测试数据,请输出能够购买大米的最多重量,你可以假设经费买不光所有的大米,并且经费你可以不用完。每个实例的输出占一行。

Sample Input

1
8 2
2 100 4
4 100 2

Sample Output

400

思路

其实这就是多重背包,但会发现用最赤裸裸的方法是过不了的,连二进制优化也都不行。那下面就来介绍一下用单调队列来实现的时间复杂度为O(nm)的多重背包。
先回顾最赤裸裸的多重背包做法。
For (int i=1;i<=n;i++)
For (int j=m;j>=0;j–)
For (int k=1;k<=s[i]&&kw[i]>=j;k++)
F[j]=min(f[j],f[j-k
w[i]]+v[i]k);
很显然前两个循环是少不了的,那么我们来想如何去掉第三个循环,从而使时间复杂度变为我们想要的O(nm)。
优化的原理是啥呢?w[i]表示物品重量,v[i]表示价值,c[i]表示数量,我们知道朴素状态转移方程:
令 c[i] = min(num[i], j / w[i])
f[i][j] = max(f[i-1][j-k
w[i]] + kv[i]) (1 <= k <= c[i]) 这里的 k 是指取第 i 种物品 k 件。
如果令 a = j / w[i] , b = j %w[i] 那么 j = a * w[i] + b.
这里用 k 表示的意义改变, k 表示取第 i 种物品的件数比 a 少几件。
那么 f[i][j] = max(f[i-1][b+k
w[i]] - kv[i]) + av[i] (a-c[i] <= k <= a)
可以发现,f[i-1][b+kw[i]] - kv[i] 只与 k 有关,而这个 k 是一段连续的。我们要做的就是求出 f[i-1][b+kw[i]] - kv[i] 在 k 取可行区间内时的最大值。

这就可以使用单调队列优化。发现在模v[i]的同一个剩余类下的所有体积的决策点都是连续的(虽然看起来是离散,但是是连续的离散),可以用单调队列维护,所以我们先按照剩余类分类,对于每一个剩余类用一个单调队列维护,这样时间复杂度就只有O(nm)

代码

#include<iostream>
#include<cstdio>
#include<deque>
#include<cstring> 
using namespace std;
int f[110];
struct jgt
{
    
    
	int s,x;
};
deque<jgt>que;
int main()
{
    
    
	int T,i,j,k,n,m,v,w,c,tem,ans;
	jgt t;
	for(scanf("%d",&T);T;T--)
	{
    
    
		scanf("%d%d",&m,&n);
		memset(f,0,sizeof(f));
		for(i=1;i<=n;i++)
		{
    
    
			scanf("%d%d%d",&w,&v,&c);
			c=min(c,m/w);
			for(j=0;j<w;j++)
			{
    
    
				que.clear();//清空队列 
				for(k=0;k<=(m-j)/w;k++)
				{
    
    
					tem=f[j+k*w]-k*v;
					for(;!que.empty()&&tem>=que.back().s;que.pop_back());//删除小于等于tem的数 
					for(;!que.empty()&&k-que.front().x>c;que.pop_front());//删除不在范围内的数 
					t.x=k,t.s=tem;
					que.push_back(t);
					f[j+k*w]=max(f[j+k*w],que.front().s+k*v);
				}
			}
		}
		printf("%d\n",f[m]);
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_46975572/article/details/113103984
今日推荐