APIO2010巡逻 分类讨论+树的直径
Solution:
① K=0:
一棵树,每条边必须经过两次,ans=2*(n-1)。
② K=1:
考虑加边后形成的环。
对于环上的边我们发现能且仅仅经过一次,非环上的边不变。
因此,ans=2*(n-1)-环长。
什么时候答案最小?
显然就是当新加入的边连向树的直径两端点时,环最长,答案也最小。
③ K=2:
讨论一下两环的位置情况:
<1>、两环不相交:那么由②可知答案继续减小,答案怎么求在下面。
<2>、两环相交:为了遍历完所有的边,那么重复的部分等价于又需要经过两次,所以答案增加。
具体答案怎么多少,怎么求???
先给出算法:把直径(长度为L1)求出来后,再把直径上的边全部取负,再求一遍直径(长度为L2),
答案就是 ans=2*(n-1)-(L1-1)-(L2-1) !!!
最巧妙的一步就是把直径给取负!
可以发现,
如果得到的新的直径 经过了原来的直径,就会对应减少相交部分的长度,对应情况<2>。否则对应情况<1>。
另:这个题还有一个很有意义的就是充分考察了对直径求法的运用:
两遍BFS(DFS)可以处理出直径长度和直径的相关信息,因此第一遍必须用这种方法求。
树形DP求直径则可以处理边权为负的情况,所以第二遍又必须用这种方法求。
Code↓:
#include <bits/stdc++.h>
#define RG register
#define IL inline
#define LL long long
#define DB double
using namespace std;
IL int gi() {
char ch=getchar(); int x=0,q=0;
while(ch<'0'||ch>'9') q=ch=='-'?1:q,ch=getchar();
while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=getchar();
return q?-x:x;
}
const int N=1e5+10;
const int INF=0x3f3f3f3f;
int n,K,L1,L2,tot,head[N],fa[N],tag[N],dis[N];
struct EDGE{int next,to;}e[N<<1];
IL void make(int a,int b) {
e[++tot]=(EDGE){head[a],b},head[a]=tot;
e[++tot]=(EDGE){head[b],a},head[b]=tot;
}
void dfs(int x,int fx) {
RG int i,y;
for (i=head[x],fa[x]=fx;i;i=e[i].next)
if ((y=e[i].to)!=fx)
dis[y]=dis[x]+1,dfs(y,x);
}
void Tree_DP(int x,int fx) {
RG int i,y,ver;
for (i=head[x];i;i=e[i].next)
if ((y=e[i].to)!=fx) {
Tree_DP(y,x),ver=(tag[x]&&tag[y])?-1:1;
L2=max(L2,dis[x]+dis[y]+ver);
dis[x]=max(dis[x],dis[y]+ver);
}
}
int main()
{
RG int i,x,y,S,s,Max;
n=gi(),K=gi();
for (i=1;i<n;++i) x=gi(),y=gi(),make(x,y);
for (i=2,S=1,Max=0,dfs(1,0);i<=n;++i)
if (dis[i]>Max) Max=dis[i],S=i;
dis[S]=0,dfs(S,0);
for (i=1,s=S;i<=n;++i)
if (dis[i]>L1) L1=dis[i],s=i;
if (K==1) printf("%d\n",2*(n-1)-(L1-1));
else {
while (s) tag[s]=1,s=fa[s];
memset(dis,0,sizeof(dis));
Tree_DP(1,0);
printf("%d\n",2*(n-1)-(L1-1)-(L2-1));
}
return 0;
}