@bzoj - 4379@ [POI2015] Modernizacja autostrady


@description@

给定一棵无根树,边权都是1,请去掉一条边并加上一条新边,定义直径为最远的两个点的距离,请输出所有可能的新树的直径的最小值和最大值

input
第一行包含一个正整数 n (3<=n<=500000),表示这棵树的点数。
接下来 n-1 行,每行包含两个正整数 u, v(1<=u,v<=n),表示 u 与 v 之间有一条边。

output
第一行输出五个正整数 k,x1,y1,x2,y2,其中k表示新树直径的最小值,x1,y1 表示这种情况下要去掉的边的两端点,x2,y2 表示这种情况下要加上的边的两端点。
第二行输出五个正整数 k,x1,y1,x2,y2,其中k表示新树直径的最大值,x1,y1 表示这种情况下要去掉的边的两端点,x2,y2 表示这种情况下要加上的边的两端点。
若有多组最优解,输出任意一组。

sample input
6
1 2
2 3
2 4
4 5
6 5
sample output
3 4 2 2 5
5 2 1 1 6

@solution@

设原树为 T,切掉某条边 (a, b) 后分割成树 T1, T2,接上某条边 (c, d) 过后变成新树 T'。
则新树的直径要么不经过 (c, d),即 T1, T2 的直径;要么经过 (c, d)。
我们枚举边 (a, b),则我们能够控制的只有经过 (c, d) 的路径。
实际上就是从 c 出发的最远距离 + 从 d 出发的最远距离 + 1。

如果要求解最大直径,则要经过 (c, d) 的路径尽量大,显然把 T1, T2 的直径连起来就是了。

如果要求解最小直径,则要经过 (c, d) 的路径尽量小,方法是把 T1, T2 的直径的中点连起来。
证明可以用一个点出发的最远距离的终点一定是直径的端点这样一个结论。

dfs 的时候,维护出父亲那一部分的联通块的直径长度与该结点为根的子树中的直径长度即可。

子树直径是一个经典的树形 dp。
父亲那一部分,考虑从父亲的父亲到父亲增加的几种路径:父亲为根的这棵子树内不经过父亲的路径,经过父亲的路径,由父亲为根的这棵子树向上经过父亲的父亲的路径。

@accepted code@

#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 500000 + 5;
const int INF = int(1E9);
struct edge{
	int to; edge *nxt;
}edges[2*MAXN], *adj[MAXN], *ecnt=&edges[0];
void addedge(int u, int v) {
	edge *p = (++ecnt);
	p->to = v, p->nxt = adj[u], adj[u] = p;
	p = (++ecnt);
	p->to = u, p->nxt = adj[v], adj[v] = p;
}
int fa[MAXN], mxl[MAXN], dp[MAXN];
void dfs1(int x) {
	int sec = 0; dp[x] = mxl[x] = 0;
	for(edge *p=adj[x];p;p=p->nxt) {
		if( p->to == fa[x] ) continue;
		fa[p->to] = x; dfs1(p->to);
		dp[x] = max(dp[x], dp[p->to]);
		if( mxl[p->to] > mxl[x] ) {
			sec = mxl[x];
			mxl[x] = mxl[p->to];
		}
		else if( mxl[p->to] > sec )
			sec = mxl[p->to];
	}
	dp[x] = max(dp[x], sec + mxl[x]);
	mxl[x]++;
}
int mn, mx, m1, m2;
void dfs2(int x, int nw, int k) {
	if( x != 1 ) {
		if( nw + dp[x] + 1 > mx ) {
			mx = nw + dp[x] + 1;
			m2 = x;
		}
		if( max(max(dp[x], nw), (dp[x] + 1)/2 + (nw + 1)/2 + 1) < mn ) {
			mn = max(max(dp[x], nw), (dp[x] + 1)/2 + (nw + 1)/2 + 1);
			m1 = x;
		}
	}
	int a = 0, b = 0, c = 0, d = 0, e = 0;
	for(edge *p=adj[x];p;p=p->nxt) {
		if( p->to == fa[x] ) continue;
		if( mxl[p->to] >= mxl[a] ) {
			c = b;
			b = a;
			a = p->to;
		}
		else if( mxl[p->to] >= mxl[b] ) {
			c = b;
			b = p->to;
		}
		else if( mxl[p->to] >= mxl[c] ) {
			c = p->to;
		}
		if( dp[p->to] >= dp[d] ) {
			e = d;
			d = p->to;
		}
		else if( dp[p->to] >= dp[e] ) {
			e = p->to;
		}
	}
	for(edge *p=adj[x];p;p=p->nxt) {
		if( p->to == fa[x] || p->to == a || p->to == b ) continue;
		dfs2(p->to, max(max(mxl[a] + mxl[b], dp[p->to == d ? e : d]), max(nw, mxl[a] + k)), max(k, mxl[a]) + 1);
	}
	if( a ) dfs2(a, max(max(mxl[b] + mxl[c], dp[a == d ? e : d]), max(nw, mxl[b] + k)), max(k, mxl[b]) + 1);
	if( b ) dfs2(b, max(max(mxl[a] + mxl[c], dp[b == d ? e : d]), max(nw, mxl[a] + k)), max(k, mxl[a]) + 1);
}
int que[MAXN], dis[MAXN], n;
int bfs(int s, int x) {
	for(int i=1;i<=n;i++)
		dis[i] = INF;
	int hd = 1, tl = 0, mx = s;
	que[++tl] = s; dis[s] = 0;
	while( hd <= tl ) {
		int f = que[hd++];
		if( dis[f] > dis[mx] ) mx = f;
		for(edge *p=adj[f];p;p=p->nxt) {
			if( p->to == x ) continue;
			if( dis[f] + 1 < dis[p->to] ) {
				que[++tl] = p->to;
				dis[p->to] = dis[f] + 1;
			}
		}
	}
	return mx;
}
int arr[MAXN];
void dfs3(int x, int y, int d, int fa) {
	arr[d] = x;
	if( x == y ) printf(" %d", arr[d/2]);
	for(edge *p=adj[x];p;p=p->nxt) {
		if( p->to == fa ) continue;
		dfs3(p->to, y, d + 1, x);
	}
}
int main() {
	scanf("%d", &n);
	for(int i=1;i<n;i++) {
		int u, v; scanf("%d%d", &u, &v);
		addedge(u, v);
	}
	mn = n, mx = 0;
	dfs1(1), dfs2(1, 0, 0);
	printf("%d %d %d", mn, m1, fa[m1]);
	dfs3(bfs(fa[m1], m1), bfs(bfs(fa[m1], m1), m1), 0, 0);
	dfs3(bfs(m1, fa[m1]), bfs(bfs(m1, fa[m1]), fa[m1]), 0, 0);
	puts("");
	printf("%d %d %d", mx, m2, fa[m2]);
	printf(" %d", bfs(fa[m2], m2));
	printf(" %d", bfs(m2, fa[m2]));
	puts("");
}

@details@

这么水的题为什么会这么少人做啊。。。

posted @ 2019-02-12 16:05  Tiw_Air_OAO  阅读(172)  评论(0编辑  收藏  举报