1. 程式人生 > 其它 >洛谷 P6326 - Shopping(點分治+樹上揹包)

洛谷 P6326 - Shopping(點分治+樹上揹包)

洛谷題面傳送門

真的是好久沒寫過題解了,先水一篇再說(

首先看到樹上連通塊問題,一眼樹形 DP,然後發現需要揹包合併,\(\mathcal O(nm^2)\),寄。

我們冷靜一下,發現對於揹包這類結構,合併的複雜度高達容量的平方,但單點插入的複雜度卻不是太高(如果使用二進位制拆分 / 單調佇列,則複雜度則是 \(\Theta(m\log D)\) / \(\Theta(m)\)),這就啟示我們使用插入 instead of 合併。有什麼結構支援合併呢?考慮點分治,在點分治過程中,我們不妨假設分治中心必選,那麼我們相當於要找一個權值最大的樹上連通塊滿足其包含根。對於這一類問題,我們考慮一個經典的“父親傳給兒子,在子樹裡繞一圈再傳回父親”的套路,即我們考慮從根節點出發,當我們 DFS 到 \(x\)

時,我們遍歷其所有子節點 \(y\),然後將 \(x\) 的 DP 值賦給 \(y\),然後在 \(y\) 子樹裡掃一遍後再令 \(x\) 的 DP 值對 \(y\) 的 DP 值取 \(\max\),這就是所謂的“樹上連通塊套路”。

下面的程式碼使用二進位制拆分實現,時間複雜度 \(nm\log n\log D\),當然也可以使用單調佇列,時間複雜度 \(nm\log n\)

const int MAXN = 500;
const int MAXM = 4000;
const int INF = 0x3f3f3f3f;
int n, m, w[MAXN + 5], c[MAXN + 5], d[MAXN + 5];
int hd[MAXN + 5], to[MAXN * 2 + 5], nxt[MAXN * 2 + 5], ec = 0;
void adde(int u, int v) {to[++ec] = v; nxt[ec] = hd[u]; hd[u] = ec;}
int siz[MAXN + 5], mx[MAXN + 5], cent; bool vis[MAXN + 5];
int dp[MAXN + 5][MAXM + 5], res;
void clear() {
	memset(hd, 0, sizeof(hd)); ec = 0; memset(vis, 0, sizeof(vis));
	mx[0] = INF; cent = res = 0; memset(dp, 0xcf, sizeof(dp));
}
void findcent(int x, int f, int tot) {
	siz[x] = 1; mx[x] = 0;
	for (int e = hd[x]; e; e = nxt[e]) {
		int y = to[e]; if (y == f || vis[y]) continue;
		findcent(y, x, tot); siz[x] += siz[y]; chkmax(mx[x], siz[y]);
	}
	chkmax(mx[x], tot - siz[x]);
	if (mx[x] < mx[cent]) cent = x;
}
vector<int> pt;
void findpts(int x, int f) {
	pt.pb(x);
	for (int e = hd[x]; e; e = nxt[e]) {
		int y = to[e]; if (y == f || vis[y]) continue;
		findpts(y, x);
	}
}
void ins(int x, int num, int cst, int val) {
	if (!num) return;
	int sum = 0, cur = 1;
	while (sum + cur <= num) {
		for (int i = m; i >= cst * cur; i--) chkmax(dp[x][i], dp[x][i - cst * cur] + val * cur);
		sum += cur; cur <<= 1;
	}
	for (int i = m; i >= cst * (num - sum); i--) chkmax(dp[x][i], dp[x][i - cst * (num - sum)] + val * (num - sum));
}
void dfs(int x, int f) {
	for (int e = hd[x]; e; e = nxt[e]) {
		int y = to[e]; if (y == f || vis[y]) continue;
		for (int i = c[y]; i <= m; i++) dp[y][i] = dp[x][i - c[y]] + w[y];
		ins(y, d[y] - 1, c[y], w[y]); dfs(y, x);
		for (int i = 0; i <= m; i++) chkmax(dp[x][i], dp[y][i]);
	}
	for (int i = 0; i <= m; i++) chkmax(res, dp[x][i]);
}
void divcent(int x) {
//	printf("divcent %d\n", x);
	pt.clear(); findpts(x, 0); vis[x] = 1;
	for (int y : pt) memset(dp[y], 0xcf, sizeof(dp[y]));
	dp[x][c[x]] = w[x]; ins(x, d[x] - 1, c[x], w[x]); dfs(x, 0);
	for (int e = hd[x]; e; e = nxt[e]) {
		int y = to[e]; if (vis[y]) continue; cent = 0;
		findcent(y, x, siz[y]); divcent(cent);
	}
}
void solve() {
	scanf("%d%d", &n, &m); clear();
	for (int i = 1; i <= n; i++) scanf("%d", &w[i]);
	for (int i = 1; i <= n; i++) scanf("%d", &c[i]);
	for (int i = 1; i <= n; i++) scanf("%d", &d[i]);
	for (int i = 1, u, v; i < n; i++) scanf("%d%d", &u, &v), adde(u, v), adde(v, u);
	findcent(1, 0, n); divcent(cent);
	printf("%d\n", res);
}
int main() {
	int qu; scanf("%d", &qu);
	while (qu--) solve();
	return 0;
}