LOJ #6039「雅礼集训 2017 Day5」珠宝

发布时间 2023-08-10 21:32:06作者: came11ia

给定 \(n\) 个物品,第 \(i\) 个物品有体积 \(c_i\),价值 \(v_i\)。给定 \(K\),对 \(1 \sim K\) 的所有 \(i\) 求大小为 \(i\) 的背包的最大价值。
\(n \leq 10^6\)\(K \leq 5 \times 10^4\)\(c_i \leq 300\)\(0 \leq v_i \leq 10^9\),时限 \(\text{2.0s}\)


注意到 \(c_i\) 范围很小,考虑往 \(\mathcal{O}(CK)\) 之类的东西上靠一靠。我们把 \(c_i\) 相同的物品放在一起并按 \(v_i\) 排序,那么取的一定是一段前缀。更进一步地,设 \(g_i\)\(c_k = i\) 的物品对应的结果,即 \(g_{i,j}\) 为取 \(j\) 个体积为 \(i\) 的物品的最大价值,那么 \(g_i\) 是凸的。

所以我们就搞出了 \(\mathcal{O}(C)\) 个凸包,很乱斗。考虑每次怎么把一个凸包合并到答案上,假设枚举了当前的体积 \(c\),那么答案数组中下标 \(\bmod c\) 不同的位置互不干扰,可以独立做。对每个同余类拉出来之后 DP 大概长成 \(f_i \gets f_j + g_{c,i-j}\) 这个样子,由于 \(g_c\) 上凸并且单调不降,因此它满足四边形不等式,于是有决策单调性,分治即可做到 \(\mathcal{O}(CK\log K)\)

code
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef vector <LL> vi;
constexpr int M = 3e2 + 5, K = 5e4 + 5, N = 1e6 + 5; 
int n, k, C;
vi f[M], ans, p, q, tmp; int L[M]; 
void trans(int l, int r, int ql, int qr) {
	if (l > r) return;
	int m = l + r >> 1;
	LL mx = 0, pos = 0;
	int rb = min(qr, m);
	for (int i = ql; i <= rb; i++) {
		LL val = 0;
		if (m - i >= q.size()) val = q.back();
		else val = q[m - i];
		if (p[i] + val > mx) mx = p[i] + val, pos = i; 
	}
	tmp[m] = mx;
	trans(l, m - 1, ql, pos);
	trans(m + 1, r, pos, qr);
}
int main() {
	ios :: sync_with_stdio(false);
	cin.tie(nullptr);
	freopen("jewelry.in", "r", stdin);
	freopen("jewelry.out", "w", stdout);
	cin >> n >> k;
	for (int i = 1, c, v; i <= n; i++) {
		cin >> c >> v;
		f[c].push_back(v);
		L[c]++;
		C = max(C, c);
	}	
	for (int i = 1; i <= C; i++) {
		f[i].push_back(0);
		sort(f[i].begin(), f[i].end()), reverse(f[i].begin(), f[i].end());
		for (int j = 1; j < f[i].size(); j++) {
			f[i][j] = f[i][j] + f[i][j - 1];
		} 
		for (int j = f[i].size() - 1; j >= 1; j--) f[i][j] = f[i][j - 1];
		f[i][0] = 0; 
		while (L[i] > K) f[i].pop_back(), L[i]--;
	} 
	ans.resize(k + 1);
	tmp.resize(k + 1);
	for (int c = 1; c <= C; c++) {
		q.clear();
		for (int i = 0; i <= L[c]; i++) q.push_back(f[c][i]);
		for (int i = 1; i <= c; i++) {
			p.clear();
			int j = i;
			if (i == c) p.push_back(0); 
			while (j <= k) p.push_back(ans[j]), j += c;
			trans(0, p.size() - 1, 0, p.size() - 1);
			j = i;
			int o = 0;
			if (i == c) o = 1;
			while (j <= k) ans[j] = tmp[o], j += c, o++;
		}
		for (int i = 1; i <= k; i++) ans[i] = max(ans[i], ans[i - 1]);	
	} 
	for (int i = 1; i <= k; i++) cout << ans[i] << " \n"[i == k];
	return 0;
} 
/*
5 10
3 2
1 48
3 25
2 76
4 83
*/