Knapsack Problem with Limitations II - 题解【DP,贪心】

题面

这是AIZU上的一道练习题,搬运VJ的链接:Knapsack Problem with Limitations II

You have N items that you want to put them into a knapsack. Item i has value vi, weight wi and limitation mi.

You want to find a subset of items to put such that:

  • The total value of the items is as large as possible.
  • The items have combined weight at most W, that is capacity of the knapsack.
  • You can select at most mi items for i-th item.

Find the maximum total value of items in the knapsack.

Input

N W
v1 w1 m1
v2 w2 m2
:
vN wN mN

The first line consists of the integers N and W. In the following N lines, the value, weight and limitation of the i-th item are given.

Output

Print the maximum total values of the items in a line.

Constraints

  • 1N50
  • 1vi50
  • 1wi109
  • 1mi109
  • 1W109

Sample Input 1

4 8
4 3 2
2 1 1
1 2 4
3 2 2

Sample Output 1

12

Sample Input 2

2 100
1 1 100
2 1 50

Sample Output 2

150

Sample Input 3

5 1000000000
3 5 1000000000
7 6 1000000000
4 4 1000000000
6 8 1000000000
2 5 1000000000

Sample Output 3

1166666666

大意

多重背包问题,但是它的重量和数量都非常大,而物品的种类数和价值都非常小。

题解

这道题也是一个经典的大范围贪心小范围DP的策略。但是,本题需要先在小范围DP,求出每一个可能的价值对应的最小背包容量,然后再对每一个值进行贪心处理。因此,首先,我们把这些物品按照性价比先排序。

然后,考虑到物品种类不超过50种,每个物品的价值不超过50,因此可以先考虑小范围,每样物品选出50件,这样物品的总价值就在150×50×50=125000之间。dp[i]代表选取物品总价值为i的时候背包容量的最小值,使用动态规划的方法求出每一个可能的价值对应的最小容量。当然,起先这道题我不会做,去VJ上看了一下别人的思路,发现有人每样物品取2件也能过……反之这个定范围就很玄学,多试几次可能就过了呢。反之,选的多肯定是好的,只要不爆复杂度。大概就是这样求的:

//DP数组在一开始被初始化为一个极大值INF
dp[0] = 0;
rep(i, 1, N) {
	ll maxc = min((ll)50, a[i].m);	//取50个物品,不足则全取
	a[i].m -= maxc;
	while (maxc--) {		//做多重背包,因为数量不多,不进行二进制压缩也可以
		rrep(j, 125000, a[i].v) {
			dp[j] = min(dp[j], dp[j - a[i].v] + a[i].w);
		}
	}
}

最后,枚举每一个价值对应的最小容量,进行贪心选择处理,每次更新最大值就可以了。大概是这样做的:

rep(i, 1, 125000) {		//对DP数组里每一个最小容量
	ll cw = W - dp[i];	//当前还剩下的背包容量
	if (cw < 0) {		//如果已经超过了现有容量,就忽略
		continue;
	}
	ll cp = i;		//当前价值,由前述dp[i]的定义可知
	for (int j = 1; j <= N && cw > 0; ++j) {	//贪心选择
		ll cnt = min(ll(cw / a[j].w), a[j].m);	//能塞入的最多物品个数与剩余个数之间取min
		cw -= cnt * a[j].w;
		cp += cnt * a[j].v;
	}
	ans = max(ans, cp);	//更新答案
}

时间复杂度……本题小DP部分复杂度是O(n),但是它带的常数是6250000……贪心部分的时间复杂度是O(n),它带的常数项是125000。考虑到n不超过50,因此在1秒内还是能跑完的。最后实测结果在200到300毫秒之间。

代码

#include <bits/stdc++.h>
#define GRP int T;cin>>T;rep(C,1,T)
#define FAST ios::sync_with_stdio(false);cin.tie(0);
#define rep(i,a,b) for(int i=a;i<=b;++i)
#define rrep(i,a,b) for(int i=a;i>=b;--i)
#define elif else if
#define mem(arr,val) memset(arr,val,sizeof(arr))
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
ll N, W;
struct node {
	ll w, v, m;
	node() = default;
	node(ll w, ll v, ll m): w(w), v(v), m(m) {}
};
node a[55];
ll dp[125010];
ll ans;
int main() {
	FAST
	mem(dp, 0x3f);
	ans = 0;
	cin >> N >> W;
	rep(i, 1, N) {
		cin >> a[i].v >> a[i].w >> a[i].m;
	}
	sort(a + 1, a + 1 + N, [](const node & c, const node & d)->bool{
		return c.v * d.w > c.w * d.v;
	});
	dp[0] = 0;
	rep(i, 1, N) {
		ll maxc = min((ll)50, a[i].m);
		a[i].m -= maxc;
		while (maxc--) {
			rrep(j, 125000, a[i].v) {
				dp[j] = min(dp[j], dp[j - a[i].v] + a[i].w);
			}
		}
	}
	rep(i, 1, 125000) {
		ll cw = W - dp[i];
		if (cw < 0) {
			continue;
		}
		ll cp = i;
		for (int j = 1; j <= N && cw > 0; ++j) {
			ll cnt = min(ll(cw / a[j].w), a[j].m);
			cw -= cnt * a[j].w;
			cp += cnt * a[j].v;
		}
		ans = max(ans, cp);
	}
	cout << ans << endl;
	return 0;
}
/*
          _           _    _            _
    /\   | |         | |  | |          (_)
   /  \  | | _____  _| |__| | ___  _ __ _ _ __   __ _
  / /\ \ | |/ _ \ \/ /  __  |/ _ \| '__| | '_ \ / _` |
 / ____ \| |  __/>  <| |  | | (_) | |  | | | | | (_| |
/_/    \_\_|\___/_/\_\_|  |_|\___/|_|  |_|_| |_|\__, |
                                                 __/ |
                                                |___/
*/
posted @   AlexHoring  阅读(141)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示