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
", ret);
	}


	return 0;
}

  

原文地址:https://www.cnblogs.com/cxhscst2/p/8508676.html