BZOJ4379: [POI2015]Modernizacja autostrady

111 篇文章 0 订阅
26 篇文章 0 订阅

题目大意:给定一棵无根树,边权都是1,去掉一条边并加上一条新边,输出所有可能的新树的直径中的最小值和最大值


一看就知道肯定是treeDP了

我们可以考虑对于每一条边,假设把它删掉,会得到两棵小树

此时新树的最大直径肯定是把两个小树的直径连起来,使得L=L1+L2+1

而最小直径肯定是连接两个直径的中点,使得L≈max(L1,L2)

所以我们需要得知对于每条边两边的小树的直径分别是多少,这个可以用两次treeDP求出

第一次就是以1为根求出每个子树的直径,这个很好求

那第二次怎么计算整个的树中刨去一个子树后剩下的那棵树的直径呢?

这棵树的直径一定是由分割点的父节点转移而来,我们只需求出在这次扩展之后新产生的链,即过这个父节点的最长链

所以在第一次treeDP时,还需要维护对于每个子树以根为端点的最长链,和次长链

那如果现在要求的就是刨去包含最长链或次长链的那棵子树的直径呢?

所以还要维护第三长链.....

这样就知道要删哪条边了


然后第二问就随便搞搞(可以O(N)暴力),求出要在哪两个点之间加边就好了


#include<iostream>
#include<cstdio>
#include<cstring>
#define N 1000010
using namespace std;
int to[N],nxt[N],pre[N],cnt;
void ae(int ff,int tt)
{
	cnt++;
	to[cnt]=tt;
	nxt[cnt]=pre[ff];
	pre[ff]=cnt;
}
int fa[N],d1[N],fir[N],sec[N],thr[N];
int fd[N],sd[N];
void dfs(int x)
{
	int i,j;
	for(i=pre[x];i;i=nxt[i])
	{
		j=to[i];
		if(j==fa[x]) continue;
		fa[j]=x;
		dfs(j);
		thr[x]=max(fir[j]+1,thr[x]);
		if(thr[x]>sec[x]) swap(thr[x],sec[x]);
		if(sec[x]>fir[x]) swap(sec[x],fir[x]);
		d1[x]=max(d1[x],d1[j]);
		sd[x]=max(d1[j],sd[x]);
		if(sd[x]>fd[x]) swap(sd[x],fd[x]);
	}
	d1[x]=max(d1[x],fir[x]+sec[x]);
}
int minn=707185547,x4,y4;
int maxn,x5,y5;
int cal(int x,int y)
{
	if(x>y) swap(x,y);
	int yy=y;
	x=x/2+x%2;y=y/2+y%2;
	return max(x+y+1,yy);
}
void solve(int x,int d2,int lg)
{
//	cout<<d1[x]<<' '<<x<<' '<<d2<<' '<<lg<<endl;
	int t,tmp;
	if(x!=1)
	{
		t=cal(d1[x],d2);
	//	cout<<fa[x]<<' '<<x<<' '<<d1[x]<<' '<<d2<<endl;
		if(t<minn) minn=t,x4=fa[x],y4=x;
		if(d1[x]+d2+1>maxn) maxn=d1[x]+d2+1,x5=fa[x],y5=x;
	}
	int i,j;
	for(i=pre[x];i;i=nxt[i])
	{
		j=to[i];
		if(j==fa[x]) continue;
		t=fir[x];tmp=fd[x];
		if(tmp==d1[j]) tmp=sd[x];
		if(t==fir[j]+1)
		{
			t=sec[x];
			tmp=max(tmp,sec[x]+max(thr[x],lg));
		}
		else if(sec[x]==fir[j]+1) tmp=max(tmp,fir[x]+max(thr[x],lg));
		else tmp=max(tmp,fir[x]+max(sec[x],lg));
		t=max(lg,t);tmp=max(tmp,d2);
		solve(j,tmp,t+1);
	}
}
int t[5],T,ma;
bool del[N];
void dfs1(int x,int ff,int dd)
{
	int i,j;
	if(dd>ma) ma=dd,t[T]=x;
	for(i=pre[x];i;i=nxt[i])
	{
		j=to[i];
		if(j==ff||del[i]) continue;
		dfs1(j,x,dd+1);
	}
}
int ans[3],TT;
bool getans(int x,int ff,int dd)
{
	if(x==t[T])
	{
		if(ma==0) ans[TT]=x;
		return true;
	}
	int i,j;
	for(i=pre[x];i;i=nxt[i])
	{
		j=to[i];
		if(j==ff||del[i]) continue;
		if(getans(j,x,dd+1))
		{
			if(dd==ma/2) ans[TT]=x;
			return true;
		}
	}
	return false;
}
void findmin(int x,int y)
{
	int i;
	for(i=pre[x];i;i=nxt[i])
	if(to[i]==y) del[i]=true;
	for(i=pre[y];i;i=nxt[i])
	if(to[i]==x) del[i]=true;
	ma=-1;T=1;
	dfs1(x,y,0);
	ma=-1;T=2;
	dfs1(t[1],0,0);
	TT=1;
	getans(t[1],0,0);
	ma=-1;T=3;
	dfs1(y,x,0);
	ma=-1;T=4;
	dfs1(t[3],0,0);
	TT=2;
	getans(t[3],0,0);
	printf("%d %d %d %d %d\n",minn,x,y,ans[1],ans[2]);
	for(i=pre[x];i;i=nxt[i])
	if(to[i]==y) del[i]=false;
	for(i=pre[y];i;i=nxt[i])
	if(to[i]==x) del[i]=false;
}
void findmax(int x,int y)
{
	int i;
	for(i=pre[x];i;i=nxt[i])
	if(to[i]==y) del[i]=true;
	for(i=pre[y];i;i=nxt[i])
	if(to[i]==x) del[i]=true;
	ma=-1;T=1;
	dfs1(x,y,0);
	ma=-1;T=2;
	dfs1(t[1],0,0);
	ma=-1;T=3;
	dfs1(y,x,0);
	ma=-1;T=4;
	dfs1(t[3],0,0);
	printf("%d %d %d %d %d",maxn,x,y,t[1],t[3]);
}
int main()
{
	int n,m;
	scanf("%d",&n);
	int i,j,x,y;
	for(i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		ae(x,y);ae(y,x);
	}
	dfs(1);
	solve(1,0,0);
	findmin(x4,y4);
	findmax(x5,y5);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值