AtCoder Beginner Contest 373 F

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 kiviki2 。他希望选择的物品在所有类型中总幸福感最大,同时总权重不超过 W W W 。请计算他能获得的最大总幸福感。

数据范围:

  • 1 ≤ N ≤ 3000 1 \leq N \leq 3000 1N3000
  • 1 ≤ W ≤ 3000 1 \leq W \leq 3000 1W3000
  • 1 ≤ w i ≤ W 1 \leq w_i \leq W 1wiW
  • 1 ≤ v i ≤ 1 0 9 1 \leq v_i \leq 10^9 1vi109
思路:

每个物品体积为 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(nmwim),可以看出时间复杂度和 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 1i 的物品中选择体积不超过 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=0maxij(fi1,jk×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)=v12×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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

离你很远的地方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值