【題解】「JOISC 2019 Day3」指定城市
阿新 • • 發佈:2022-05-28
給定一棵樹,雙向邊,每條邊兩個方向的權值分別為 \(C_i, D_i\),多次詢問 \(k\),表示選出 \(k\) 個點,依次將以每個點為根的內向樹邊權賦值為 \(0\),需要求出最後樹的邊權之和的最小值。
當 \(k=1\) 的時候,我們求出 \(w_x\) 表示以 \(x\) 為根的內向樹邊權和,總和減去 \(\max\{w\}\) 即為答案,\(w\) 可以用換根 DP 求得。
考慮 \(k > 1\) 的情況,有一個關鍵結論是詢問 \(k + 1\) 的答案一定是 \(k\) 的答案基礎上,加上一個點。
注意當 \(k = 1\) 的時候結論不成立!只有 \(k > 1\)
我們先考慮 \(k = 2\) 怎麼做,不難直接推出如果選擇兩個點 \(x,y\),最大刪除的邊的和為 \(\dfrac{w_x + w_y + dis(x,y)}{2}\),其中 \(dij(x,y)\) 表示 \(x,y\) 之間路徑的雙向邊權和。這個式子是直徑的格式,直接二次掃描換根可以求得。
然後在這條直徑的基礎上增加點,就相當於將直徑縮成一個點,並以之為根,每次增加一條根到葉子的路徑。這是個經典問題,直接長鏈剖分後排序選擇即可。
那麼這個關鍵結論怎麼證明呢,因為 \(k = 2\) 時選擇的是直徑,所以在此基礎上新增一個點,不會修改原直徑,因為不可能有比直徑更長的路徑。\(k = 1\) 就純屬只存在一個點的特例,所以不滿足結論。
時間複雜度 \(\mathcal{O}(N\log N)\),瓶頸在於排序,基數排序可以優化至線性。
#define N 200005 int n, m, h[N], tot = 1; LL w[N], sum, ed[N], d[N], f[N], v[N]; struct edge{int to, nxt, val;}e[N << 1]; void add(int x,int y,int z){e[++tot].nxt = h[x], h[x] = tot, e[tot].to = y, e[tot].val = z;} void dfs(int x,int fa){for(int i = h[x]; i; i = e[i].nxt)if(e[i].to != fa)dfs(e[i].to, x), w[x] += w[e[i].to] + e[i].val;} void calc(int x,int fa){for(int i = h[x]; i; i = e[i].nxt)if(e[i].to != fa)w[e[i].to] = w[x] - e[i].val + e[i ^ 1].val, calc(e[i].to, x);} void Dfs(int x,int fa){f[x] = fa; for(int i = h[x]; i; i = e[i].nxt)if(e[i].to != fa)d[e[i].to] = d[x] + e[i].val + e[i ^ 1].val, Dfs(e[i].to, x);} vector<LL>c; LL solve(int x,int fa){ LL cur = 0; for(int i = h[x]; i; i = e[i].nxt)if(e[i].to != fa){ LL w = e[i ^ 1].val + solve(e[i].to, x); if(!cur)cur = w; else c.pb(min(cur, w)), cmx(cur, w); }return cur; } int main() { read(n); rp(i, n - 1){ int x, y, l, r; read(x, y, l, r), sum += l + r; add(x, y, r), add(y, x, l); } dfs(1, 0), calc(1, 0); rp(i, n)cmx(ed[1], w[i]); Dfs(1, 0); int A = 1; rp(i, n)if(d[i] + w[i] > d[A] + w[A])A = i; d[A] = 1; Dfs(A, 0); int B = 1; rp(i, n)if(d[i] + w[i] > d[B] + w[B])B = i; ed[2] = (d[B] + w[A] + w[B]) / 2; int x = B; while(x)v[x] = 1, x = f[x]; x = B; while(x){ for(int i = h[x]; i; i = e[i].nxt)if(!v[e[i].to]) c.pb(solve(e[i].to, x) + e[i ^ 1].val); x = f[x]; } sort(c.begin(), c.end()), reverse(c.begin(), c.end()); int t = 2; go(x, c)ed[t + 1] = ed[t] + x, t++; while(t < n)ed[t + 1] = ed[t], t++; read(m); while(m--){int x; read(x); printf("%lld\n", sum - ed[x]);} return 0; }