zoukankan      html  css  js  c++  java
  • 树的直径&&题解 P3629 【[APIO2010]巡逻】

    虽然有很多Dalao发过很多非常详细的题解,但是本蒟蒻还是(凭着初生牛犊不怕虎的精神)来发一篇题解。

    因为最近正在复习原来学习的图论,正巧一点也不巧)看到了这道题,然后就……点了进来

    其实刚看到这道题的时候,还是有一点懵的(当然,这是一道图论是没得跑的)。 然后,我就从头理了一下思路。
    首先:
    1、该图上的所有道路都要到达。
    2、所有操作都是从一号点开始,一号点结束。
    3、要使得巡逻距离较短。

    第一步

    我们先考虑不加边的情况
    从一号点出发,要把每个边遍历一遍再回到1号点,会恰好经过每条边2次,总路线哦不,是经过的路线总长为2(n-1)。


    第二步

    我们来考虑 k==1 的情况。
    建立一条新边,因为每一次新建的边都要。

    恰好 经过一次

    因为这是一棵树,所以当我们加上一条边时就构成了一个环,而这个环便可以使这个图在遍历时可以减少重复走的边。 如图(不是很非常丑): 

    因为这个环所带来的重复路径的减少,所以我们在构建环时要找到此时树中最长的链进行构造,这样的话我们就可以满足题上的最短解,这个时候我们来推一下公式:

    2(n-1)-L+1
    如果你已经想到这里,那么恭喜你,你已经成功地拿到了30分。下面我们来看如何AC这道题。


    第三步

    第一条路建完以后,我们来考虑第二条路。当第二条新路(u,v)建立之后,又会形成一个环。当连个环不重叠的时候,答案会一直缩小。但是如果有重叠部分,就会有一部分的路不会被巡逻到,所以我们就只好将车重新巡逻这条边,最后返回。这步操作最终又使那条边重复经过了一次。
    具体见图: 

    这样的话,我们就得到了最短的路径。
    总结一下具体的步骤:
    1、先在原树上找到该树的直径,记为L1,然后将边权改为-1(后面会解释为什么是-1)。
    2、在取反边权后再次寻找该树的直径记为L2。
    3、计算答案。

    公式

    由前面的讲解可推知:

    ans=2(n-1)-(L1-1)-(L2-1)
    即
    ans=2n-L1-L2

    时间复杂度的话就十分的直观为O(n)。


    前面的思路就讲解到这里了,下面我们来讲一讲代码实现。

    首先

    这道题的一个最直观的考点——树的直径
    所以下面就介绍一下树的直径这个考点。
    树的直径简单来说就是树中最长的链,下面将有两种O(n)的方法来求树的直径。
    背景:假设树以N个节点N-1条边以无向图的形式给出并以邻接表的形式给出。

    第一种:树形DP求树的直径

    我们用d[x]表示距离,f[x]则表示任意两点之间的距离。
    代码实现:

    void dp(int x){
        v[x]=1;
        for(int i=head[x];i;i=e[i].next){
            int y=e[i].to;
            if(v[y])continue;
            dp(y);
            ans=max(ans,dis[x]+dis[y]+e[i].w);
            dis[x]=max(dis[x],dis[y]+e[i].w);
        }
    }

    用树形dp来求解的话,一个十分明显的优点就是可以处理负权制的问题


    第二种:两次BFS求树的直径

    通过两次BFS求出直径,更易算出直径上的直径上的具体节点。
    步骤 :
    ①从树上任意一点出发P,找到与其距离最大的点M
    ②从点M出发,找到与其距离最大的点N
    ③MN即为树的直径
    下面是具体证明(证明过程摘自老师ppt)。

    反证法:假设M不是直径的一个端点,AB是树的直径。

    ① 如果P是直径上的点,如图,PM>PB则AP+PM>AP+PB=AB这与AB是直径矛盾。 

    ②-1 若P不在直径上:P到M路径与A到B路径有公共结点TPT+TM>PT+TB,则TM>TB,故AT+TM>AT+TB=AB,矛盾

    ②-2 P到M的路径与A到B的路径无公共结点PC+CM>PC+CD+BD,则CM>CD+BD,CM+CD>BD故CM+CD+AD>BD+AD=AB,矛盾。



    这样两边BFS的正确性就证明完毕。
    代码:

     1 void bfs(int x,int ck){
     2     memset(dis,0,sizeof(dis));
     3     memset(vis,0,sizeof(vis));
     4     queue<int >q;
     5     q.push(x);
     6     vis[x]=1;
     7     while(!q.empty()){
     8         int now=q.front();
     9         q.pop();
    10         for(int i=head[now];i;i=e[i].next){
    11             int y=e[i].to;
    12             if(!vis[y]){
    13                 vis[y]=1;
    14                 dis[y]=dis[now]+e[i].w;
    15                 q.push(y);
    16                 if(ck)f[y]=now;//记录父节点,修改时会用
    17             }
    18         }
    19     }
    20     for(int i=1;i<=n;i++){
    21         if(ans<dis[i])ans=dis[i],point=i;
    22     }
    23 }
     

    两遍BFS求树的直径的优点很明显,但是缺点也很突出,就是无法处理负权值。
    所以我们在处理过第一次的直径后将权值赋为了-1,就是这个原因。
    下面就是这道题的AC代码了

      1 #include<bits/stdc++.h>
      2 using namespace std;
      3 template<typename type>
      4 void scan(type &x){
      5     type f=1;x=0;char s=getchar();
      6     while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
      7     while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
      8     x*=f;
      9 }
     10 const int N=1e5+7;
     11 struct node{
     12     int to,w,next;
     13 }e[N*2];
     14 int head[N],cnt;
     15 void add(int a,int b,int c){
     16     e[++cnt].to=b;
     17     e[cnt].next=head[a];
     18     head[a]=cnt;
     19     e[cnt].w=1;
     20 }
     21 int dis[N],vis[N],l1,l2,n,k,point,f[N],d[N],v[N];
     22 int ans=0;
     23 void bfs(int x,int ck){
     24     memset(dis,0,sizeof(dis));
     25     memset(vis,0,sizeof(vis));
     26     queue<int >q;
     27     q.push(x);
     28     vis[x]=1;
     29     while(!q.empty()){
     30         int now=q.front();
     31         q.pop();
     32         for(int i=head[now];i;i=e[i].next){
     33             int y=e[i].to;
     34             if(!vis[y]){
     35                 vis[y]=1;
     36                 dis[y]=dis[now]+e[i].w;
     37                 q.push(y);
     38                 if(ck)f[y]=now;//记录父节点,修改时会用
     39             }
     40         }
     41     }
     42     for(int i=1;i<=n;i++){
     43         if(ans<dis[i])ans=dis[i],point=i;
     44     }
     45 }
     46 void change(int a){//修改边权值
     47     while(f[a]){
     48         int fa=f[a];
     49         for(int i=head[fa];i;i=e[i].next){
     50                 // e[i].w=-1;
     51                 if(e[i].to==a){
     52                 //    e[i].w=e[i^1].w=-1;
     53                    e[i].w=-1;
     54                    break; 
     55                 }
     56         }
     57         for(int i=head[a];i;i=e[i].next){
     58             if(e[i].to==fa){
     59                 e[i].w=-1;
     60                 break;
     61             }
     62         }
     63         a=fa;
     64     }
     65 }
     66 void dp(int x){
     67     v[x]=1;
     68     for(int i=head[x];i;i=e[i].next){
     69         int y=e[i].to;
     70         if(v[y])continue;
     71         dp(y);
     72         ans=max(ans,dis[x]+dis[y]+e[i].w);
     73         dis[x]=max(dis[x],dis[y]+e[i].w);
     74     }
     75 
     76 }
     77 
     78 int main(){
     79    scan(n);scan(k);
     80     // scanf("%d%d",&n,&k);
     81     for(int i=1;i<n;i++){
     82         int a;int b;
     83        scan(a);scan(b);
     84         // scanf("%d%d",&a,&b);
     85         add(a,b,1);
     86         add(b,a,1);
     87     }
     88     if(k==1){
     89         bfs(1,0);
     90         bfs(point,0);
     91         printf("%d",2*n-dis[point]-1);
     92     }else{
     93         bfs(1,0);
     94         bfs(point,1);
     95         l1=dis[point];
     96         change(point);
     97         memset(dis,0,sizeof(dis));
     98         memset(vis,0,sizeof(vis));
     99         ans=0;
    100         dp(1);
    101         // printf("%d
    ",l1);
    102         // printf("%d
    ",ans);
    103         printf("%d",2*n-ans-l1);
    104     }
    105     return 0;
    106 }
    View Code

    希望大家关注一波。

  • 相关阅读:
    ImportError: FloatProgress not found. Please update jupyter and ipywidgets.问题
    运行jupyter notebook显示ImportError: cannot import name 'secure_write',导致始终显示In[*]问题,服务器挂掉
    anaconda中jupyter notebook安装代码自动补全插件,报错“Exception: Jupyter command `jupyter-contrib` not found.”
    Hadoop集群(第7期)_Eclipse开发环境设置
    Hadoop集群(第6期)_WordCount运行详解
    Hadoop集群(第5期)_Hadoop安装配置
    Hadoop集群(第4期)_SecureCRT使用
    Hadoop集群(第3期)_VSFTP安装配置
    Hadoop(2)_机器信息分布表
    Hadoop(1)
  • 原文地址:https://www.cnblogs.com/xishirujin/p/11104303.html
Copyright © 2011-2022 走看看