0.Description
在一棵树上加1或2条边,新加的边必须走一次。求出从1出发走过所有点回到1的最小路程。
1.Solution
首先可以算出加边前总共要走的路程是 ((n-1) imes2) 。
加边操作实际上就是将某一段回溯的路程缩短,由于这个题加的边数非常少,所以我们分类讨论一下。
加一条边
要想让这条边缩短最大的路程,我们可以直接去求树上最长的路径——直径,然后直接减去直径的长度,再加上强制走的这条加边。
加两条边
感性理解可得两条边的第一条一定也是加直径(证明有时间补上)。接下来我们要选剩下的最长路径,但显而易见的是,此时我们选的路径如果和第一条有交集,那么交集部分还要多走一遍,于是我们可以把这些边权都赋值为-1,可以在最后减去路径长度时方便地表示多走一条边,然后就直接求直径即可,此时可以保证选到的边与第一条路径交集最小。注意:由于此时边权有负数,求树的直径时需要用dp。
总结
我们先求出树的直径的长度,记为 (res_1) ,如果加一条边则输出 ((n-1) imes2-res_1+1) 。如果加两条边,则按照上面的方法求出第二条路径的长度,记为 (res_2) ,输出 ((n-1) imes2-res_1+1-res_2+1) 。
2.Code
#define N 100010
int n,k,bg,ed,vis[N];
int res1,res2,pos,dis[N],f[N],fa[N];
struct Edge {
int to,nxt,wei;
}e[N<<1];
int head[N],cnt=-1;
inline void ade(int u,int v,int w){
e[++cnt].to=v,e[cnt].wei=w;
e[cnt].nxt=head[u],head[u]=cnt;
}
//第一次求树的直径,用两次DFS
void DFS(int now,int ff){
cerr<<now<<" "<<ff<<endl;
if(dis[now]>res1){
res1=dis[now],pos=now;
}
fa[now]=ff;
for(rg int i=head[now];~i;i=e[i].nxt){
int v=e[i].to,w=e[i].wei;
if(v!=ff){
dis[v]=dis[now]+w;
DFS(v,now);
}
}
}
//第二次求树的直径,用dp
void DFS2(int now,int ff){
for(rg int i=head[now];~i;i=e[i].nxt){
int v=e[i].to,w=e[i].wei;
if(v!=ff){
DFS2(v,now);
res2=max(res2,f[now]+f[v]+w);
f[now]=max(f[now],f[v]+w);
}
}
}
//把第一条路径上的边权赋为-1
void Modify(int now,int ff){
for(rg int i=head[now];~i;i=e[i].nxt){
int v=e[i].to;
if(v!=ff){
if(vis[v])e[i].wei=e[i^1].wei=-1;//注意把反向边也更改了
Modify(v,now);
}
}
}
int main(){
Read(n),Read(k);
memset(head,-1,sizeof(head));
for(rg int i=1;i<n;i++){
int u,v;Read(u),Read(v);
ade(u,v,1),ade(v,u,1);
}
DFS(1,0),bg=pos,pos=0,res1=0;
memset(dis,0,sizeof(dis));
memset(fa,0,sizeof(fa));
DFS(bg,0),ed=pos,pos=0;
while(ed)vis[ed]=1,ed=fa[ed];//标记第一条路径
Modify(bg,0);
DFS2(bg,0);
if(k==1)cout<<(n-1)*2-res1+1<<endl;
else cout<<(n-1)*2-res1+1-res2+1<<endl;
return 0;
}