TJOI 2017 城市 题解

题目传送门

题目大意: 给一棵边带权的树,你可以将一条边换个位置,换完之后还得是一棵树,要求换完之后树的直径最小。

题解

O ( n ) O(n) 做法太强了,只能想到 O ( n 2 ) O(n^2) 的做法……

考虑枚举删去哪一条边,假如删掉当前枚举的边,那么整棵树会被分成两个部分,然后我们要把这条边重新找个位置,假如我们选择了连接部分 1 1 中的 x x 和部分 2 2 中的 y y ,那么这棵树的直径就是:

max { 1 2 1 x + 2 y + } \max\{部分1的直径,\\部分2的直径,\\在部分1中以x为起点的最长链+在部分2中以y为起点的最长链+删去的边的长度\}

直径很好求,最长链也是 d p dp 一下就可以了,要分两次 d p dp ,第一次求出每个点往自己子树内走的最长链,第二次利用上一次的 d p dp 出来的结果,求出每个点往父亲方向走的最长链。

最后找到每个部分内 延申出去的最长链 最短 的点就是我们要链接的点了。

代码如下:

#include <cstdio>
#include <algorithm>
using namespace std;
#define maxn 5010

int n,ans=2147483640;
struct edge{int x,y,z,next;};
edge e[maxn<<1];
int first[maxn],len=0;
void buildroad(int x,int y,int z)
{
	e[++len]=(edge){x,y,z,first[x]};
	first[x]=len;
}
struct par{
	int mi,ma;
	par(int x,int y):mi(x),ma(y){}
	void operator +=(const par &b){mi=min(mi,b.mi);ma=max(ma,b.ma);}
};
int f[maxn],from[maxn];
void dp1(int x,int fa)
{
	f[x]=from[x]=0;
	for(int i=first[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(y==fa)continue;
		dp1(y,x);
		if(f[y]+e[i].z>f[x])f[x]=f[y]+e[i].z,from[x]=y;
	}
}
par dp2(int x,int fa,int to_fa)
{
	par p(max(f[x],to_fa),f[x]+to_fa);
	int ci=0;
	for(int i=first[x];i;i=e[i].next)
	if(e[i].y!=from[x]&&e[i].y!=fa)ci=max(ci,f[e[i].y]+e[i].z);
	
	for(int i=first[x];i;i=e[i].next)
	{
		int y=e[i].y;
		if(y==fa)continue;
		if(y==from[x])p+=dp2(y,x,max(ci,to_fa)+e[i].z);
		else p+=dp2(y,x,max(f[x],to_fa)+e[i].z);
	}
	return p;
}

int main()
{
	scanf("%d",&n);
	for(int i=1,x,y,z;i<n;i++)
	scanf("%d %d %d",&x,&y,&z),buildroad(x,y,z),buildroad(y,x,z);
	for(int i=1,x,y;i<=len;i+=2)
	{
		x=e[i].x;y=e[i].y;
		dp1(x,y);dp1(y,x);
		par re1=dp2(x,y,0),re2=dp2(y,x,0);
		ans=min(ans,max(re1.mi+re2.mi+e[i].z,max(re1.ma,re2.ma)));
	}
	printf("%d",ans);
}
发布了234 篇原创文章 · 获赞 100 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/103956345
今日推荐