題意:
給一棵樹,找到三個頂點,使三個頂點兩兩之間路徑的並集最大
思路:
必定會有一組最優解,使得 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; }