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; }