1. 程式人生 > 其它 >【題解】「JOISC 2019 Day3」指定城市

【題解】「JOISC 2019 Day3」指定城市

給定一棵樹,雙向邊,每條邊兩個方向的權值分別為 \(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 = 1 \to k =2\) 發現不滿足條件就排除了,結果裂開。以後做這種結論題還要考慮到 corner case。

我們先考慮 \(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;
}