【JZOJ1437】【COCI2008】道路重组

Description

N个点N-1条无向边的组成的连通图,现在允许你删除一条边,再添加一条边,在保证连通的前提下,使得最远的两个点之间的距离尽可能小。

Input

第一行包含一个整数N(1<=N<=300000),表示点数,编号为1到N。
  接下来N-1行,每行包含两个整数,表示一条边连接的两个点。

Output

第一行输出重组后的最远两个点的距离的最小值。

Sample Input

输入1:
4
1 2
2 3
3 4

输入2:
7
1 3
2 3
2 7
4 3
7 5
3 6

Sample Output

输出1:
2

输出2:
3

Hint

【数据范围】
  40%的数据N<=30;
  70%的数据N<=3000;

思路

首先这是一棵树

显然我们必须改变那条直径(否则答案只会变大)

所以我们枚举直径上的边把它删掉,于是他们就分成两棵树。

只要连接那两棵树的直径的中点即可。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=310000;
int st[N],en[N*2],next[N*2],fa[N],n,d[N*2],height[N],mh[N][2],f1[N],f2[N];
bool t[N];
int a[N][2];
void add(int x,int p[])
{
	if (p[0]<x) p[1]=p[0],p[0]=x; else
	if (p[1]<x) p[1]=x;
}
void build_tree(int root)
{
	d[1]=root;
	height[root]=1;
	fa[root]=0;
	int top=1;
	for(int j=1; j<=n; j++)
	{
		int x=d[j];
		for(int i=st[x]; i; i=next[i])
		if (fa[x]!=en[i])
		{
			d[++top]=en[i];
			height[en[i]]=height[x]+1;
			fa[en[i]]=x;
		}
	}
	memset(mh,0,sizeof(mh));
	for(int i=n; i>=1; i--)
	{
		int x=d[i];
		add(height[x],mh[x]);
		add(mh[x][0],mh[fa[x]]);
	}
}
int que[N*2][2];
void bfs(int cen,int po)
{
	for(int i=st[cen];i;i=next[i])
	if (t[en[i]]==0)
	{
		int top=0,tail=1,pl=1;
		que[1][0]=en[i];
		que[1][1]=1;
		t[en[i]]=1;
		if (mh[en[i]][1])
		f1[po]=f2[po]=max(f1[po],mh[en[i]][0]+mh[en[i]][1]-height[en[i]]*2);
		while (top<tail)
		{
			int now=que[++top][0],len=que[top][1];
			for(int i=st[now];i;i=next[i])
			if (t[en[i]]==0)
			{
				if (mh[en[i]][1])
				f1[po]=f2[po]=max(f1[po],mh[en[i]][0]+mh[en[i]][1]-height[en[i]]*2);
				que[++tail][0]=en[i];
				que[tail][1]=len+1;
				pl=max(pl,len+1);
				t[en[i]]=1;
			}
		}
		if (a[po][0]<pl) a[po][1]=a[po][0],a[po][0]=pl; else
		if (a[po][1]<pl) a[po][1]=pl;
	}
}
int pq;
void add(int x,int y)
{
	next[++pq]=st[x];
	st[x]=pq;
	en[pq]=y;
}
int main()
{
	scanf("%d",&n);
	for(int i=1; i<n; i++)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);
		add(y,x);
	}
	build_tree(1);
	int root=1;
	for(int i=1; i<=n; i++)
	if (height[i]>height[root]) root=i;
	build_tree(root);
	t[root]=1;
	int now=root,up=mh[root][0];
	while (height[now]!=mh[now][0])
	{
		for(int i=st[now];i;i=next[i])
		if (fa[now]!=en[i] && mh[en[i]][0]==mh[now][0])
		{
			t[en[i]]=1;
			bfs(now,height[now]);
			now=en[i];
			break;
		}
	}
	bfs(now,height[now]);
	f1[1]=max(f1[1],a[1][0]+a[1][1]);
	int ml=a[1][0];
	for(int i=2; i<=up; i++)
	{
		ml++;
		f1[i]=max(f1[i],f1[i-1]);
		f1[i]=max(f1[i],max(ml,a[i][1])+a[i][0]);
		ml=max(ml,a[i][0]);
	}
	f2[up]=max(f2[up],a[up][0]+a[up][1]);
	ml=a[up][0];
	for(int i=up-1; i>=1; i--)
	{
		ml++;
		f2[i]=max(f2[i+1],f2[i]);
		f2[i]=max(f2[i],max(ml,a[i][1])+a[i][0]);
		ml=max(ml,a[i][0]);
	}
	int ans=up;
	for(int i=1; i<=up-1; i++)
	{
		ans=min(ans,max((f1[i]+1)/2+(f2[i+1]+1)/2+1,max(f1[i],f2[i+1])));
	}
	printf("%d",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值