【题解】bzoj1912(同洛谷P3629)[APIO2010]巡逻 树的直径

题目链接
这里写图片描述

Input

第一行包含两个整数 n, K(1 ≤ K ≤ 2)。接下来 n – 1行,每行两个整数 a, b, 表示村庄a与b之间有一条道路(1 ≤ a, b ≤ n)。

Output

输出一个整数,表示新建了K 条道路后能达到的最小巡逻距离。

Sample Input

8 1

1 2

3 1

3 4

5 3

7 5

8 5

5 6

Sample Output

11

HINT

10%的数据中,n ≤ 1000, K = 1;
30%的数据中,K = 1;
80%的数据中,每个村庄相邻的村庄数不超过 25;
90%的数据中,每个村庄相邻的村庄数不超过 150;
100%的数据中,3 ≤ n ≤ 100,000, 1 ≤ K ≤ 2。


最初线路总长度为2 * (n-1),当K=1时,找到树的最长链,设长度为L1,答案为2 * (n-1)-L1+1;当K=2时,将直径边权取反,再次求直径,设长度为L2,答案为2*n-L1-L2。

#include<cstdio>
const int N=1e5+10;
struct Edge{
    int v,w,nx;
}edge[N<<1];
int head[N],tot=1,n,k,pre1[N],pre2[N],len,rt;
inline void addedge(int u,int v)
{
    edge[++tot].v=v;
    edge[tot].w=1;
    edge[tot].nx=head[u];
    head[u]=tot;
}
int dfs(int u,int fa)
{
    int mx1=0,mx2=0;//最长路与次长路 
    for(int i=head[u];i;i=edge[i].nx)
    {
        int v=edge[i].v;
        if(v!=fa)
        {
            int w=edge[i].w+dfs(v,u);
            if(w>mx1)mx2=mx1,mx1=w,pre2[u]=pre1[u],pre1[u]=i;
            else if(w>mx2)mx2=w,pre2[u]=i;
        }
    }
    if(mx1+mx2>len)len=mx1+mx2,rt=u;
    return mx1;
}
int main()
{
    //freopen("in.txt","r",stdin);
    scanf("%d%d",&n,&k);
    int u,v,ans=2*(n-1);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&u,&v);
        addedge(u,v),addedge(v,u);
    }
    dfs(1,0);ans=ans-len+1;
    if(k==2)
    {
        len=0;
        for(int i=pre1[rt];i;i=pre1[edge[i].v])edge[i].w=edge[i^1].w=-1;//成对变换修改正反边
        for(int i=pre2[rt];i;i=pre1[edge[i].v])edge[i].w=edge[i^1].w=-1;
        dfs(1,0);
        ans=ans-len+1; 
    }
    printf("%d\n",ans);
    return 0;
}

总结

通过边权取反的方法处理两个环重叠的情况,非常巧妙。

猜你喜欢

转载自blog.csdn.net/qq_41958841/article/details/82718707