洛谷 P6326 - Shopping(點分治+樹上揹包)
阿新 • • 發佈:2022-04-13
真的是好久沒寫過題解了,先水一篇再說(
首先看到樹上連通塊問題,一眼樹形 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; }