並查集有兩種優化。第一種是直接連根——雖然是O(n)但是會破壞樹形結構。
按秩合並
大意:求最小生成樹的兩個點間的最大路徑。
帶邊權的並查集?多組數據?
我們按秩合並。
基本思想是使包含較少結點的樹的根指向包含較多結點的樹的根。
我們存邊時,用結構體存邊。但不用前向星。因為如果要kruskal的話需要改動一下。本來我們連接最短的邊時,如果兩端的端點的父親不一樣的話直接連上就可以了。但是按秩排序不一樣。他既要優化又要不破壞樹形結構,我們就造一個秩rank,rank[i]表示的相當於子樹大小或者深度的東西,每次查詢就把小的連到大的上並直接把邊權直接連到父親結點上,那么就能保留原來的樹形結構並做到優化。
#include<iostream> #include<cstdio> #include<algorithm> #include<ctype.h> #include<cstring> using namespace std; const int maxn=50010; inline int read() { int x=0,w=1;char c=getchar(); while(!isdigit(c)){ if(c=='-')w=-1; c=getchar(); } while(isdigit(c))x=(x<<3)+(x<<1)+(c^48),c=getchar(); return x*w; } int n,m,t; int fa[maxn],e[maxn],rank[maxn]; struct data{ //這是邊 int x,y,z; }a[maxn]; inline bool cmp(data a,data b){return a.z<b.z;} inline int find(int x){return fa[x]==x?x:find(fa[x]);} inline void kruskal() { int s=0,f1,f2; for(int i=1;i<=n;i++)fa[i]=i,rank[i]=1; for(int i=1;i<=m;i++) { f1=find(a[i].x); f2=find(a[i].y); if(f1!=f2) { if(rank[f1]<rank[f2]) { fa[f1]=f2; e[f1]=a[i].z; rank[f2]=max(rank[f2],rank[f1]+1); }//高度大的做小的的根 else{ fa[f2]=f1; e[f2]=a[i].z; rank[f1]=max(rank[f1],rank[f2]+1); }//相等或者相反就相反來 s++; } if(s==n-1)break; } } int c[maxn]; int query(int x,int y) { for(int i=1;i<=n;i++)c[i]=-1; int tmp=0,ans=0; while(1) { c[x]=tmp;//c是從x開始向上邊的最大值。 if(fa[x]==x)break; tmp=max(tmp,e[x]);//e代表向上連接的邊權 x=fa[x];//向上爬 } while(1) { if(c[y]>=0){ans=max(ans,c[y]);break;}//找到LCA就break if(fa[y]==y)break;//或者找到根 ans=max(ans,e[y]); y=fa[y]; }//因為一定會集中到LCA就直接用ans return ans; } int main() { while(~scanf("%d%d",&n,&m)) { for(int i=1;i<=m;i++) { a[i]=(data){read(),read(),read()}; } sort(a+1,a+1+m,cmp); kruskal(); t=read(); for(int x,y,i=1;i<=t;i++){ x=read(),y=read(); printf("%d\n",query(x,y)); } } return 0; }
但是按秩合並並不僅僅能夠處理最小生成樹問題,也可以處理一些大規模數據的動態加邊和查詢問題,每次連邊后可以記錄兩個秩的大小,非常的方便。