树的直径&&题解 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\n",l1);
102         // printf("%d\n",ans);
103         printf("%d",2*n-ans-l1);
104     }
105     return 0;
106 }
View Code

希望大家关注一波。

猜你喜欢

转载自www.cnblogs.com/xishirujin/p/11104303.html
今日推荐