比较自然的思路是,由于需要记录连通块合并时的信息,所以需要建出Kruskal重构树。
需要用LCT维护,支持加点和在线LCA操作。
不妨考虑在并查集合并的同时记录信息,pre[x]表示x与它的父亲相连的时刻。
两个点连通的时刻,等于两个点之间路径上时刻的最大值。
注意到按秩合并但不路径压缩的并查集不改变树的结构,且树高为log,正好符合要求。
$O(nlog n)$
1 #include<cstdio> 2 #include<algorithm> 3 #define rep(i,l,r) for (int i=(l); i<=(r); i++) 4 using namespace std; 5 6 const int N=1000010; 7 int n,m,op,u,v,ans,tim,he[N],fa[N],pre[N]; 8 9 int find(int x){ return (fa[x]==x) ? x : find(fa[x]); } 10 11 void uni(int u,int v,int tim){ 12 u=find(u); v=find(v); 13 if (u==v) return; 14 if (he[u]<he[v]) swap(u,v); 15 fa[v]=u; pre[v]=tim; 16 if (he[u]==he[v]) he[u]++; 17 } 18 19 int lca(int u,int v){ 20 if (find(u)!=find(v)) return 0; 21 int s1=0,s2=0,x=u,y=v,res=0; 22 while (x!=fa[x]) s1++,x=fa[x]; 23 while (y!=fa[y]) s2++,y=fa[y]; 24 while (s1>s2) res=max(res,pre[u]),u=fa[u],s1--; 25 while (s2>s1) res=max(res,pre[v]),v=fa[v],s2--; 26 while (u!=v) res=max(res,max(pre[u],pre[v])),u=fa[u],v=fa[v]; 27 return res; 28 } 29 30 int main(){ 31 freopen("bzoj4668.in","r",stdin); 32 freopen("bzoj4668.out","w",stdout); 33 scanf("%d%d",&n,&m); 34 rep(i,1,n) fa[i]=i,he[i]=1; 35 rep(i,1,m){ 36 scanf("%d%d%d",&op,&u,&v); u^=ans; v^=ans; 37 if (op==0) uni(u,v,++tim); else printf("%d ",ans=lca(u,v)); 38 } 39 return 0; 40 }