← 全部文章

ARC096F Sweet Alchemy 题解

本文目录(3 节)
  1. 题意
  2. 题解
  3. 代码

ARC096F Sweet Alchemy

题意

  • 有 nn 个物品,买一个第 ii 个物品需要花费 mim_i 元。
  • 除了第一个物品外,第 ii 个物品有一个上级物品 pi(<i)p_i(< i)。
  • 给定 dd,设第 ii 个物品买了 cic_i 个,要求对于 i(∈[2,n])i(\in [2,n]) 满足 cpi≤ci≤cpi+dc_{p_i} \le c_i \le c_{p_i} + d。
  • 一共有 xx 元,问最多能买多少个物品。
  • n≤50n \le 50,x,d,mi≤109x,d,m_i \le 10^9。

题解

设 viv_i 表示 ii 的子树大小,wiw_i 表示 ii 的子树内权值和,则原问题等价于:

有 nn 个物品和一个体积为 xx 的背包。

第 ii 个物品体积为 wiw_i 价值为 viv_i 个数为 cic_i。

求背包能装下的最大价值和。

vi≤n≤50v_i \le n \le 50,x,ci,wi≤109x,c_i,w_i \le 10^9。

这是一个经典的多重背包问题,但这里的背包体积和总价值都非常大,因此无论是以体积为状态还是以价值为状态的多重背包都行不通。

注意到物品个数和价值都很小,考虑从这个角度入手。

考虑 i,ji,j 满足 wivi≤wjvj\frac {w_i}{v_i} \le \frac {w_j}{v_j}:

  • 若 ii 拿了 vjv_j 个,体积为 wivjw_iv_j,价值为 vivjv_iv_j。
  • 若 jj 拿了 viv_i 个,体积为 wjviw_jv_i,价值为 vivjv_iv_j。

由于价值一样,但前者的体积更小,所以前者一定不劣。

因此按照 wv\frac wv 从小到大排序后,我们可以每个物品拿 nn 个出来,剩下的一定是从前往后贪心的拿。

此时总价值不超过 O(n3)\mathcal O(n^3),于是再考虑以总价值为状态的多重背包,即设 fi,jf_{i,j} 为考虑到第 ii 个物品,总价值为 jj 的最小体积。

单调队列优化是 O(n4)\mathcal O(n^4) 的,二进制拆分则是 O(n4log⁡n)\mathcal O(n^4 \log n) 的,都可以接受,但后者更好写。

代码

const int N = 57;
int n, p[N], t;
vi e[N];
ll x, d, w[N], v[N], c[N];

void dfs(int x) {
	v[x] = 1;
	for (int y : e[x])
		dfs(y), v[x] += v[y], w[x] += w[y];
}

const ll inf = 1e18;
ll W[N<<3|1], V[N<<3|1], f[N*N*N], s, ans;

int main() {
	rd(n), rd(x, d, w[1]), p[1] = 1;
	for (int i = 2, f; i <= n; i++)
		rd(w[i]), rd(f), e[f].pb(i), p[i] = i, c[i] = d;
	dfs(1), c[1] = x / w[1] * v[1];
	sort(p + 1, p + n + 1, [&](int i, int j) {
		return w[i] * v[j] < w[j] * v[i];
	});
	for (int i = 1; i <= n; i++) {
		int p = ::p[i];
		ll c = min(::c[p], (ll)n);
		s += c * v[p];
		ll k = 0, x = 0;
		while (x + (1 << k) <= c)
			x += 1 << k, V[++t] = (1 << k) * v[p], W[t] = (1 << k) * w[p], ++k;
		if (c - x) V[++t] = (c - x) * v[p], W[t] = (c - x) * w[p];
	}
	for (int i = 1; i <= s; i++) f[i] = inf;
	for (int i = 1, k = 0; i <= t; k += V[i++])
		for (int j = k; ~j; j--)
			f[j+V[i]] = min(f[j+V[i]], f[j] + W[i]);
	for (int i = 0; i <= s; i++)
		if (f[i] <= x) {
			ll now = i, sum = f[i];
			for (int j = 1; j <= n; j++) {
				int p = ::p[j];
				ll c = min(::c[p] - min(::c[p], (ll)n), (x - sum) / w[p]);
				now += c * v[p], sum += c * w[p];
			}
			ans = max(ans, now);
		}
	print(ans);
	return 0;
}

标签:DP、背包、贪心

评论

    填写昵称即可留言。评论经站主审核后公开,请勿提交私人信息。

    正在连接评论服务…