题目链接
http://www.lydsy.com/JudgeOnline/problem.php?id=1912
题目大意
给一棵树,求加
k
条边之后,从1号点遍历每个点之后再回到1号点的最小距离和。
思路
如果不加边,那么遍历走过的距离显然是
2(n−1)
。注意到一个很有意思的限制:
k=1或2
。那么可以分类讨论以下两种情况
1、
k=1
,很显然最优的方法就是在树的最长路径
x到y
之间连一条边,这样
x
到
另外为了情况2的方便,要在DFS过程中用链表记录下树的直径的路径。这个路径是两条路径相交而成的,假设这两条路径的交点为
x
:一条是
除非次长路为0(
2、 k=2 ,肯定是找树中次长路径 x′到y′ ,给 x′和y′ 之间连边,我们在情况1的基础上,将第一次DFS求得的树的直径上的所有边边权全部标为-1,再DFS一次,求出树中的次长路径 len2 ,最终答案为 2(n−1)−len1+1−len2+1
代码
注意情况2中标记路径的过程,分两次做,一次是修改x下面最长路的路径,一次是修改x下面的次长路的路径,也是y下面的最长路路径。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#define MAXE 100010
#define MAXV 100010
using namespace std;
int n,m;
struct edge
{
int u,v,w,next;
}edges[2*MAXE];
int head[MAXV],nCount=1;
void AddEdge(int U,int V,int W)
{
edges[++nCount].u=U;
edges[nCount].v=V;
edges[nCount].w=W;
edges[nCount].next=head[U];
head[U]=nCount;
}
int diameter,s; //树的直径为diameter,直径的起点是s
int son1[MAXV],son2[MAXV]; //记录最长路与次长路的路径
int DFS(int u,int fa)
{
int max1=0,max2=0; //最长路与次长路
for(int p=head[u];p!=-1;p=edges[p].next)
{
int v=edges[p].v;
if(v==fa) continue;
int nowh=DFS(v,u)+edges[p].w;
if(nowh>max1) max2=max1,son2[u]=son1[u],max1=nowh,son1[u]=p;
else if(nowh>max2) max2=nowh,son2[u]=p;
}
if(diameter<max1+max2) diameter=max1+max2,s=u;
return max1;
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
AddEdge(u,v,1);
AddEdge(v,u,1);
}
int ans=(n-1)<<1;
diameter=0;
memset(son1,-1,sizeof(son1));
memset(son2,-1,sizeof(son2));
DFS(1,-1);
ans-=diameter-1;
if(m>1)
{
diameter=0;
for(int i=son1[s];i!=-1;i=son1[edges[i].v]) edges[i].w=edges[i^1].w=-1;
for(int i=son2[s];i!=-1;i=son1[edges[i].v]) edges[i].w=edges[i^1].w=-1;
memset(son1,-1,sizeof(son1));
memset(son2,-1,sizeof(son2));
DFS(1,-1);
ans-=diameter-1;
}
printf("%d\n",ans);
return 0;
}