P1782 旅行商的背包 Solution

Description

有一个体积为 CCC 的背包和若干种物品.
nnn 种物品,第 iii 种体积为 viv_ivi,价值 wiw_iwi,有 did_idi 件.
mmm 种物品,每种对应一个函数 f(x)=aix2+bix+cif(x)=a_ix^2+b_ix+c_if(x)=aix2+bix+ci,表示若选 xxx 体积的这件物品,价值为 f(x)f(x)f(x).(不选当作选 000 体积
选择一些物品,使得体积和不超过 CCC,求最大价值.

Limitations

1≤n≤1041\le n\le 10^41n104
1≤m≤51\le m\le 51m5
1≤C≤1041\le C\le 10^41C104
1≤wi,vi,di≤10001\le w_i,v_i,d_i\le 10001wi,vi,di1000
∣ai∣,∣bi∣,∣ci∣≤1000|a_i|,|b_i|,|c_i|\le 1000ai,bi,ci1000
1s,128MB1\text{s},128\text{MB}1s,128MB

Solution

不考虑后 mmm 件物品,那么就是一个多重背包,然而暴力跑 DP 铁定超时,需要优化.
多重背包一般有两种优化方式:二进制拆分和单调队列.
由于常数原因,我们选用二进制拆分,将每堆物品分成若干组,每组分别有 20,21,⋯ ,2k−1,d−2k+12^0,2^1,\cdots,2^{k-1},d-2^k+120,21,,2k1,d2k+1 个(kkk 是满足 n−2k+1>0n-2^k+1>0n2k+1>0 的最大整数).
那么用这些组就可以组出 0∼d0\sim d0d 个的所有选法,这样就转化成了 010101 背包.

对于后 mmm 个物品,由于 mmm 很小,我们可以枚举给每个物品分配的体积,暴力更新.

总时间复杂度为 O(C∑log⁡di+mC2)O(C\sum\log d_i+mC^2)O(Clogdi+mC2).

Code

1.15KB,1.83s,2.55MB  (in total, C++20 with O2)1.15\text{KB},1.83\text{s},2.55\text{MB}\;\texttt{(in total, C++20 with O2)}1.15KB,1.83s,2.55MB(in total, C++20 with O2)

#include <bits/stdc++.h>
using namespace std;

using i64 = long long;
using ui64 = unsigned long long;
using i128 = __int128;
using ui128 = unsigned __int128;
using f4 = float;
using f8 = double;
using f16 = long double;

template<class T>
bool chmax(T &a, const T &b){
	if(a < b){ a = b; return true; }
	return false;
}

template<class T>
bool chmin(T &a, const T &b){
	if(a > b){ a = b; return true; }
	return false;
}


signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	
	int n, m, c;
	scanf("%d %d %d", &n, &m, &c);
	
	vector<pair<int, i64>> goods;
	for (int i = 0, v, w, d; i < n; i++) {
		scanf("%d %d %d", &v, &w, &d);
		int t = 1;
		while (d) {
			if (d - t <= 0) t = d, d = 0;
			else d -= t;
			goods.emplace_back(t * v, 1LL * t * w);
			t <<= 1;
		}
	}
	
	vector<i64> dp(c + 1);
	for (auto [v, w] : goods) 
	    for (int k = c; k >= v; k--) chmax(dp[k], dp[k - v] + w);
	
	for (int i = 0, A, B, C; i < m; i++) {
		scanf("%d %d %d", &A, &B, &C);
		auto f = [&](int x) { return 1LL * A * x * x + 1LL * B * x + C; };
		for (int k = c; k >= 0; k--)
		    for (int j = 0; j <= k; j++) chmax(dp[k], dp[k - j] + f(j));
	}
	printf("%lld", dp[c]);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值