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 \(v_i\), weight \(w_i\) and limitation \(m_i\).

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 \(m_i\) 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

  • \(1 \le N \le 50\)
  • \(1 \le v_i \le 50\)
  • \(1 \le w_i \le 10^9\)
  • \(1 \le m_i \le 10^9\)
  • \(1 \le W \le 10^9\)

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件,这样物品的总价值就在\(1\)\(50 \times 50 \times 50=125000\)之间。\(\verb|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 @ 2022-07-09 19:44  AlexHoring  阅读(130)  评论(0编辑  收藏  举报