【题解】[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;
}