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;
}
/*
_ _ _ _
/\ | | | | | | (_)
/ \ | | _____ _| |__| | ___ _ __ _ _ __ __ _
/ /\ \ | |/ _ \ \/ / __ |/ _ \| '__| | '_ \ / _` |
/ ____ \| | __/> <| | | | (_) | | | | | | | (_| |
/_/ \_\_|\___/_/\_\_| |_|\___/|_| |_|_| |_|\__, |
__/ |
|___/
*/