题外话:
你们知道想了半天思路提供给同学然后证明了半天时间复杂度然后考试结束的痛苦吗……
看到连通性,我们会本能地想起并查集(想起LCT的说明您太强了orz)
题目要求输出的是\((u,v)\)这一点对最早在什么时候联通
考虑连边操作
显然,如果\((u,v)\)已经联通,那么连接它们两个是没有意义的
如果\((u,v)\)不连通,则必须连接它们两个
经过一番思考,我们思考出来以下思路:
若连接\((u,v)\),则将\(u\)并查集合并到\(v\)并查集上
连出来的边设一个边权\(val\),val为当前为第几个铁路
好像就没有了?
对于询问,我们直接往上一层层跳,跳出LCA,然后统计答案
在实际实现的时候,每个点会记录一个\(v[i]\),表示i到fa[i]这条路径上的权值
同时,为了方便,我(在参考lzh的代码后)选取了另外一种更暴力的找LCA的方法
注意:不能路径压缩,为了保证时间复杂度我们使用按高度合并(学名不知道……)
期望?均摊?反正层数\(O(logn)\)
code:
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; const int N=500010; const int inf=2e9; inline void read(int &x) { x=0; int f=1; char ch=getchar(); while(ch<'0'||ch>'9') { if (ch=='-') { f=-1; } ch=getchar(); } while(ch>='0'&&ch<='9') { x=x*10+ch-'0'; ch=getchar(); } x*=f; } /* f[i]:常规并查集记录fa v[i]:从i到f[i]这条边的权值 h[i]:i所在子树的高度 dep[i]:i所在子树中i的深度 */ int f[N],v[N],h[N]; int dep[N]; int n,m; inline int find(int x) { while(x!=f[x]) { x=f[x]; } return x; } inline int check(int x,int y) { int fx=find(x),fy=find(y); return fx==fy; } inline void merge(int x,int y,int val) { int fx=find(x),fy=find(y); //x所在子树高度小于y所在子树高度->将x合并到y if (h[fx]<h[fy]) { f[fx]=fy; h[fy]=max(h[fy],h[fx]+1); v[fx]=val; } else { f[fy]=fx; h[fx]=max(h[fx],h[fy]+1); v[fy]=val; } } //vis[i]:i在搜索过程中是否被找过 int vis[N]; inline int find(int x,int val) { while(x!=f[x]) { vis[x]=val; x=f[x]; } vis[x]=val; return x; } inline int query(int x,int y) { find(x,1); int lca=y; while(!vis[lca]&&f[lca]!=lca) { lca=f[lca]; } if (!vis[lca]) { find(x,0); return 0; } find(x,0); int ans=0; while(x!=lca) { ans=max(ans,v[x]); x=f[x]; } while(y!=lca) { ans=max(ans,v[y]); y=f[y]; } return ans; } int main() { read(n),read(m); for(int i=1;i<=n;i++) { f[i]=i; v[i]=-inf; h[i]=dep[i]=1; } int last_ans=0,cnt=0; for(int i=1;i<=m;i++) { int opx,x,y; read(opx),read(x),read(y); x^=last_ans,y^=last_ans; if (!opx) { cnt++; if (check(x,y)) { continue; } merge(x,y,cnt); } else { printf("%d\n",last_ans=query(x,y)); } } return 0; }