1. 程式人生 > 其它 >【題解】[CCO 2019] Winter Driving

【題解】[CCO 2019] Winter Driving

給定一顆樹,你需要給每條邊固定一個方向,使二元組 \((x,y)\) 總數最大,\(x,y\) 滿足存在一條從 \(x\) 出發到 \(y\) 的路徑。

很厲害的結論題。

根據樣例,我們猜測答案是以一個點為根的內向樹或外向樹,但是這非常的假。因為除了樹退化成鏈的情況,其它都不是最優。

我們手玩一下菊花圖,發現肯定是選擇一些邊從中心出發,其它邊則出發到中心。

結論一:對於菊花圖,最優解一定是將所有非中心點分為兩部分,使得兩部分 \(\sum A_i\) 的差最小。

顯然,我們要在和不變的情況下使乘積最大,就是均值不等式。兩邊儘量均分。

怎麼均分,直接揹包即可。

由此我們猜測,對於任意一顆樹,是否也像菊花圖一樣存在一箇中心點呢?

結論二:對於樹,最優解存在一箇中心點,將該點刪去後,剩下的子樹都是內向樹或者外向樹。

不大會證明,感覺挺對的。對於一個內向樹/外向樹,答案可以很方便一邊 DFS 求出。

那麼我們怎麼計算跨中心點的答案。根據結論一,我們是將這些子樹分成兩部分,使得 \(\sum A_i\) 的差最小即可。

這樣已經能拿到 \(40\) 分了。我們在觀察一些性質。

結論三:中心點一定是樹的重心。注意:是原樹的重心而不是對於 \(A\) 的加權重心。

這不難理解,本質還是均值不等式。但是我不大會嚴謹證明。

一棵樹最多隻有兩個重心,所以等價於我們只用求兩次菊花圖的答案然後取最大值。

由於值域比較大,不太能揹包。但是題目有性質 \(D\le 36\)

,所以直接折半搜尋即可。

#define N 270005
int n, sz[N], a[N], X, Y; LL sum, ans, mx;
vector<int>e[N];
void get(int x,int fa){
	sz[x] = 1; int cur = 0;
	go(y, e[x])if(y != fa)get(y, x), sz[x] += sz[y], cmx(cur, sz[y]);
	cmx(cur, n - sz[x]);
	if(cur * 2 <= n)Y = x, swap(X, Y);
}
void dfs(int x,int fa){
	sz[x] = 0;
	go(y, e[x])if(y != fa)dfs(y, x), sz[x] += sz[y];
	sum += a[x] * 1LL * (sz[x] + a[x] - 1), sz[x] += a[x];
}
int u[40], p[N], q[N], l, r;
void ins1(int x,int s){
	if(x == 19)p[++l] = s;
	else{
		if(u[x])ins1(x + 1, s + u[x]);
		ins1(x + 1, s);
	}
}
void ins2(int x,int s){
	if(x == 37)q[++r] = s;
	else{
		if(u[x])ins2(x + 1, s + u[x]);
		ins2(x + 1, s);
	}
}
LL calc(int x){
	int t = 0; mx = 0, l = r = 0;
	memset(u, 0, sizeof(u));
	go(y, e[x])u[++t] = sz[y];
	ins1(1, 0), ins2(19, 0);
	sort(p + 1, p + l + 1), sort(q + 1, q + r + 1);
	l = unique(p + 1, p + l + 1) - p - 1, 
	r = unique(q + 1, q + r + 1) - q - 1;
	int lim = (sz[x] - a[x]) / 2;
	rp(i, l){
		int j = lower_bound(q + 1, q + r + 1, lim - p[i]) - q;
		if(j <= r)cmx(mx, (p[i] + q[j]) * 1LL * (sz[x] - a[x] - p[i] - q[j]));
	}return mx;
}
void check(int x){sum = 0, dfs(x, 0), cmx(ans, sum + calc(x));}
int main() {
	read(n);
	rp(i, n)read(a[i]);
	rp(i, n - 1){
		int x; read(x);
		e[x].pb(i + 1), e[i + 1].pb(x);
	}
	get(1, 0);
	check(X); if(Y)check(Y);
	printf("%lld\n", ans);
	return 0;
}