题面:N个村庄,(N-1)条道路,构成了一棵树,每条道路权值为1。警察在节点1,现要修建K条新道路,要巡逻每个
节点,警察必须经过 新道路 正好一次 。求最小巡逻距离。
数据范围:3 ≤ N ≤ 1e5,1 ≤ K≤ 2。(搜狗输入法的符号大全真是个好东西)
K的范围是1,2,显然可以分情况讨论。
在不修建新道路时,相当于把整棵树遍历一遍,每条边恰好被经过两遍,路线总长度为2(N-1)。
修建一条新道路时,找到树的直径L,连接两端,形成环,此时直径上的道路只需要经过一次就够了,答案就是:2(N-1)-L₁+1。
修建两条道路时,找到次长链,同样的又会形成环,但如果两个环有重叠,只经过一次的道路又会被打回原形走两次。仔细思考,得到一种方法,能有效处理重叠:①在最初的树上求直径L₁,然后把直径上的边权取反(从1改为-1)。②再次求直径L₂。答案就是:2N - L₁ - L₂ 。
可读性非常强的AC代码:
#include<bits/stdc++.h>
using namespace std;
inline int read()
{
char ch=getchar();
int s=0,f=0;
while (!(ch>='0'&&ch<='9'))
{f|=ch=='-';ch=getchar();}
while (ch>='0'&&ch<='9')
{s=(s<<3)+(s<<1)+ch-'0';ch=getchar();}
return f? -s:s;
}
const int maxx=1e5+10;
struct edge{int y,v,next;}e[maxx<<1];
int lin[maxx],len=1;//len=1 为后面^做铺垫,使边^1后得到对应的反向边
int n,k,d,di;
int max1[maxx],s1[maxx],s2[maxx];
int ans=0;
void insert(int xx,int yy){e[++len].next=lin[xx];e[len].y=yy;e[len].v=1;lin[xx]=len;}
void dfs(int x,int fa)
{
int max2=0;//x到其子树中次远节点的距离
max1[x]=0;//x到最远节点的距离
for(int i=lin[x];i;i=e[i].next)//枚举子节点
{
if(e[i].y==fa) continue;
dfs(e[i].y,x);//处理子节点的各个值
if(max1[e[i].y]+e[i].v > max1[x])
{
max2=max1[x]; s2[x]=s1[x];
max1[x]=max1[e[i].y]+e[i].v; s1[x]=i;
}
else if(max1[e[i].y]+e[i].v > max2)
max2=max1[e[i].y]+e[i].v,s2[x]=i;
}
if(max1[x]+max2>d) d=max1[x]+max2,di=x;//更新直径
}
int main()
{
freopen("test.in","r",stdin);
freopen("test.out","w",stdout);
n=read();k=read();
for(int i=1;i<n;i++)
{
int x=read(),y=read();
insert(x,y);insert(y,x);
}
ans=(n-1)*2;
d=0;
dfs(1,0);
ans-=d-1;//即ans-=(d-1)
if(k==1)
{
printf("%d",ans);
return 0;
}
//di为转折点
for(int i=s1[di];i;i=s1[e[i].y])//di到其子树节点的最远距离
e[i].v=e[i^1].v=-1;//双向边 都要变成-1 所以^1
//注意s1!!因为先走di的次远边,后来就一直走最远边啦!
for(int i=s2[di];i;i=s1[e[i].y])//di到其子树节点的次远距离
e[i].v=e[i^1].v=-1;
memset(max1,0,sizeof(max1));
d=0;
dfs(1,0);
ans-=d-1;
printf("%d",ans);
return 0;
}