ZOJ 3949 (17th 浙大校赛 B题,树型DP)
题目链接 The 17th Zhejiang University Programming Contest Problem B
题意 给定一棵树,现在要加一条连接$1$(根结点)和$x$的边,求加了这条边之后,所有点到根结点的距离的和的最小值。
输出这个最小值即可。
当加的这条边为$1-x$时,$x$和$1$的中点及以下的所有点到根结点的距离都发生了变化,其他点都没有发生改变。
现在设$ans[i]$表示当加的这条边为$1-x$时的答案,考虑答案从某个点转移到他的儿子。
首先树型DP预处理出$ans[1]$。
当$x$为$1$的儿子的时候,这时加的边为重边,所以$ans[x] = ans[1]$。
在处理的时候设$c[dep]$为当前深度为$dep$的点。
其他时候,令$x$和$1$的中点为$u$,这个时候$x$和$x$的父亲相比,以$u$为根的子树这个部分到$1$的距离要加$1$(更远了)。
但是有一部分的点例外,那就是以$x$为根的子树,直接通过$x$到$1$,而不是通过$x$的父亲到$1$。
所以考虑刚刚加的$1$,这一部分的值要减$2$。
时间复杂度$O(n)$
#include <bits/stdc++.h> using namespace std; #define rep(i, a, b) for (int i(a); i <= (b); ++i) #define dec(i, a, b) for (int i(a); i >= (b); --i) typedef long long LL; const int N = 2e5 + 10; int T, n; int sz[N], deep[N]; int c[N]; LL f[N]; LL ans[N], all, ret; vector <int> v[N]; void dfs(int x, int fa, int dep){ sz[x] = 1; f[x] = 0; deep[x] = dep; for (auto u : v[x]){ if (u == fa) continue; dfs(u, x, dep + 1); sz[x] += sz[u]; f[x] += 0ll + f[u] + sz[u]; } } void solve(int x, int fa, int dep){ for (auto u : v[x]){ if (u == fa) continue; c[dep] = u; if (deep[u] >= 2) ans[u] = ans[x] + sz[c[dep / 2 + 1]] - 2 * sz[u]; else ans[u] = ans[x]; solve(u, x, dep + 1); } } int main(){ scanf("%d", &T); while (T--){ scanf("%d", &n); rep(i, 0, n + 1) v[i].clear(); rep(i, 2, n){ int x, y; scanf("%d%d", &x, &y); v[x].push_back(y); v[y].push_back(x); } dfs(1, 0, 0); ans[1] = f[1]; c[0] = 0; solve(1, 0, 1); ret = ans[1]; rep(i, 2, n) ret = min(ret, ans[i]); printf("%lld\n", ret); } return 0; }