正题
题目大意
刚开始一棵树,在树中加入k条边(k<=2)使得这些边都得走过的情况下,每个点都到达并回到原点的最少边。
解题思路
首先我们发现如果不加边的话答案是2∗(n−1)2*(n-1)2∗(n−1)。
之后我们考虑k=1k=1k=1的情况,我们找树的直径,然后在两个端点之间加边,这样就可以少跑直径长度-1这么多。设直径长度为disdisdis,答案是2∗(n−1)−dis+12*(n-1)-dis+12∗(n−1)−dis+1。
之后我们考虑k=2k=2k=2的情况。我们发现如果之后再找一条直径和之前的直径没有重边的话答案就是2∗(n−1)−dis−dis2+22*(n-1)-dis-dis2+22∗(n−1)−dis−dis2+2。
有重边怎么办,我们发现如果有重边,那么重边就会多跑一次。所以我们可以把第一次直径上所有边权都变成−1-1−1,我们就可以直接2∗(n−1)−dis−dis2+22*(n-1)-dis-dis2+22∗(n−1)−dis−dis2+2。
根据zyczyczyc的说法,第一次用dfsdfsdfs方便记录路径,第二次有负边权所以得用dpdpdp。
完美解决该题
codecodecode
#include<cstdio>
#include<algorithm>
#include<cstring>
#define N 100010
using namespace std;
int n,k,dis,dis2,ls[N],tot;
int pre[N],last,f[N],p,x,y;
struct node{int to,w,next;
}a[2*N];
void addl(int x,int y,int w)//加边
{a[++tot].to=y;a[tot].w=w;a[tot].next=ls[x];ls[x]=tot;
}
void dfs(int x,int fa,int disr)//第一次求直径
{if(disr>dis) dis=disr,p=x;for(int i=ls[x];i;i=a[i].next)if(a[i].to!=fa)pre[a[i].to]=x,dfs(a[i].to,x,disr+a[i].w);
}
void change(int x,int end)//改变边权
{if(x==end) return;for(int i=ls[x];i;i=a[i].next)if(a[i].to==pre[x]){a[i].w=-1;a[i^1].w=-1;change(a[i].to,end);}
}
void dp(int x,int fa)//第二次求直径
{for(int i=ls[x];i;i=a[i].next)if(a[i].to!=fa){int y=a[i].to;dp(a[i].to,x);dis2=max(dis2,f[x]+f[y]+a[i].w);f[x]=max(f[y]+a[i].w,f[x]);}
}
int main()
{tot=1;scanf("%d%d",&n,&k);for(int i=1;i<n;i++){scanf("%d%d",&x,&y);addl(x,y,1);addl(y,x,1);}dfs(1,0,0);last=p;dis=0;dfs(p,0,0);if(k==1){printf("%d",2*(n-1)-dis+1);return 0;}change(p,last);dp(1,0);printf("%d",2*(n-1)-dis-dis2+2);
}