F - Knapsack with Diminishing Values
有 N N N 种物品。其中 i i i 类物品的权重为 w i w_i wi ,价值为 v i v_i vi 。每种类型都有 1 0 10 10^{10} 1010 个可用物品。
高桥打算选择一些物品,把它们装进一个容量为 W W W 的袋子里。他希望最大化所选物品的价值,同时避免选择过多相同类型的物品。因此,他将选择 i i i 类型的 k i k_i ki 件物品的幸福定义为 k i v i − k i 2 k_i v_i - k_i^2 kivi−ki2 。他希望选择的物品在所有类型中总幸福感最大,同时总权重不超过 W W W 。请计算他能获得的最大总幸福感。
数据范围:
- 1 ≤ N ≤ 3000 1 \leq N \leq 3000 1≤N≤3000
- 1 ≤ W ≤ 3000 1 \leq W \leq 3000 1≤W≤3000
- 1 ≤ w i ≤ W 1 \leq w_i \leq W 1≤wi≤W
- 1 ≤ v i ≤ 1 0 9 1 \leq v_i \leq 10^9 1≤vi≤109
思路:
每个物品体积为 w i w_i wi,初始价值为 v i v_i vi。每次选择物品之后,其价值会减少。如果按照 分组背包 的思路,每个物品最多可以选 m w i \frac{m}{w_i} wim 次,时间复杂度为 O ( n ⋅ m ⋅ m w i ) O(n \cdot m \cdot \frac{m}{w_i}) O(n⋅m⋅wim),可以看出时间复杂度和 w i w_i wi 有关。如果所有物品的 w i = 1 w_i=1 wi=1,那么时间复杂度会非常高。
观察到 w i w_i wi 的数据范围比较小,因此可以优化这个过程。我们按照体积分组,设 f i , j f_{i,j} fi,j 表示从体积为 1 ∼ i 1 \sim i 1∼i 的物品中选择体积不超过 j j j 的最大价值。转移方程类似于分组背包:
f
i
,
j
=
max
k
=
0
⌊
j
i
⌋
(
f
i
−
1
,
j
−
k
×
i
+
g
i
,
k
)
f_{i,j} = \max_{k=0}^{\left\lfloor \frac{j}{i} \right\rfloor} \left( f_{i-1,j-k \times i} + g_{i,k} \right)
fi,j=k=0max⌊ij⌋(fi−1,j−k×i+gi,k)
其中,
g
i
,
k
g_{i,k}
gi,k 表示体积为
i
i
i 的物品选择
k
k
k 个的最大价值。
现在考虑如何计算出
g
g
g 数组,将所有物品按体积分组,相同体积的物品存入堆中,依次取出价值最大的物品。假设某个物品选择
k
+
1
k+1
k+1 次的价值为
F
(
k
+
1
)
F(k+1)
F(k+1),选择
k
k
k 次的价值为
F
(
k
)
F(k)
F(k),则第
k
+
1
k+1
k+1 次选获得的价值为:
F
(
k
+
1
)
−
F
(
k
)
=
v
−
1
−
2
×
k
F(k+1)-F(k) = v-1-2 \times k
F(k+1)−F(k)=v−1−2×k
可以看出若每次都取同一个物品的话,获得的价值是一个等差数列。每次取出物品后,下一次选择的价值相比之前减少 2。可以通过每次将减少的价值重新放回堆中来维持动态选择。
时间复杂度为 O ( m 2 log m ) O(m^2 \log m) O(m2logm),其中 m m m 是背包的体积。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define for_(i, start, end) for(int i = (start); i <= (end); ++i)
#define set_pre(k) (cout << fixed << setprecision(k))
#define FREOPEN freopen("input.txt", "r", stdin)
const int inf = 0x3f3f3f3f, mod = 1E9 + 7;
const int N = 4E3 + 10;
ll f[N][N]; //物品的体积为 1 ~ i 体积 j 的最大价值
ll g[N][N];
void solve() {
int n, m;
cin >> n >> m;
vector<vector<int>> wl(m + 1, vector<int>());
for_(i, 1, n) {
ll w, v;
cin >> v >> w;
wl[v].push_back(w - 1);
}
for_(i, 1, m) {
priority_queue<ll> pq;
for (auto w : wl[i]) {
pq.push(w);
}
for_(j, 1, m / i) {
if (!pq.size())
break;
ll w = pq.top();
g[i][j] = g[i][j - 1] + w;
pq.pop(), pq.push(w - 2);
}
}
for_(i, 1, m) { //物品的体积
for_(j, 1, m) { //背包的体积
f[i][j] = f[i - 1][j];
for_(k, 1, j / i) { //最多能装 j/i 个
f[i][j] = max(f[i][j], f[i - 1][j - k * i] + g[i][k]);
}
}
}
cout << f[m][m] << '\n';
}
int main()
{
ios::sync_with_stdio(false), cin.tie(0);
int t = 1;
while (t--) {
solve();
}
return 0;
}