• APIO2010巡逻 分类讨论+树的直径


    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
    ",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
    ",2*(n-1)-(L1-1)-(L2-1));	
        }
        return 0;
    }
    
    

    The End

  • 相关阅读:
    Linux编程之epoll
    Linux IO模式及 select、poll、epoll详解
    与程序员相关的CPU缓存知识
    JDK源码阅读-FileOutputStream
    JDK源码阅读-FileInputStream
    JDK源码阅读-ByteBuffer
    Java如何保证文件落盘?
    Linux/UNIX编程如何保证文件落盘
    JDK源码阅读-RandomAccessFile
    JDK源码阅读-FileDescriptor
  • 原文地址:https://www.cnblogs.com/Bhllx/p/10616465.html
Copyright © 2020-2023  润新知