Codeforces1294F-Three Paths on a Tree(两次BFS求树的直径)

题意:

给一棵树,找到三个顶点,使三个顶点两两之间路径的并集最大

思路:

 

必定会有一组最优解,使得 a,b是树直径上的端点。

 

证明:

 

假设某个答案取连接点x。x最远的树到达的点是s,根据树的直径算法,s是树的某个直径a的端点。假设x的最远和第二远的点组成的链是b,b就会和a有一段公共部分。我们取a和b相交部分距离s最远的那个点y。那么取这个链上点y的答案一定比x更优  

 

用两次BFS可以求出直径的两个端点,在这个过程中还能顺便求出一个端点到树上每一点的距离。之后再用一次BFS求得另一个端点到树上每一点的距离。

 

再枚举第三个顶点c就可以求出这三个顶点了

 

最终距离为:[dis(a,b)+dis(b,c)+dis(a,c)]/2

两次BFS求树的直径与端点:

那么问题来了,怎么求树的直径的呢?这里提供一种两次BFS求树的直径的方法:

 先任选一个起点BFS找到最长路的终点,再从终点进行BFS,则第二次BFS找到的最长路即为树的直径;
 原理: 设起点为u,第一次BFS找到的终点v一定是树的直径的一个端点
 证明: 1) 如果u 是直径上的点,则v显然是直径的终点(因为如果v不是的话,则必定存在另一个点w使得u到w的距离更长,则于BFS找到了v矛盾)
     2) 如果u不是直径上的点,则u到v必然于树的直径相交(反证),那么交点到v 必然就是直径的后半段了
 所以v一定是直径的一个端点,所以从v进行BFS得到的一定是直径长度

#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#include<queue>
 using namespace std;
 const int maxn=2e5+10;
 vector<int> a[maxn];
 int n,vis[maxn],dis1[maxn],dis2[maxn],dis[maxn],pos;
 void bfs(int x)
 {
     memset(vis,0,sizeof(vis));
     memset(dis,0,sizeof(dis));
     pos=x;
     vis[x]=1,dis[x]=0;
     queue<int> q;
     q.push(x);
     while(!q.empty()){
         int u=q.front();q.pop();
         for(int i=0;i<a[u].size();i++){
             if(!vis[a[u][i]]){
                 vis[a[u][i]]=1;
                 dis[a[u][i]]=dis[u]+1;
                 q.push(a[u][i]);
                 if(dis[a[u][i]]>dis[pos]) pos=a[u][i];
             }
         }
     }
 }
 int main()
 {
     scanf("%d",&n);
     int u,v;
     for(int i=1;i<n;i++){
         scanf("%d%d",&u,&v);
         a[u].push_back(v);
         a[v].push_back(u);
     }
    int a,b,c;
    bfs(1),a=pos;
    bfs(pos),b=pos;
    for(int i=1;i<=n;i++)    dis1[i]=dis[i];
    bfs(pos);
    for(int i=1;i<=n;i++)    dis2[i]=dis[i];
    c=0;
    for(int i=1;i<=n;i++)
        if(dis1[i]+dis2[i]>dis1[c]+dis2[c]&&i!=a&&i!=b)    c=i;
    int ans=(dis1[b]+dis1[c]+dis2[c])/2;
    cout<<ans<<endl<<a<<" "<<b<<" "<<c;
     return 0;
 }
posted @ 2020-01-23 19:32  overrate_wsj  阅读(462)  评论(1编辑  收藏  举报