题目大意
给定一副
N
个点的图。动态的往图中加边。现在有两种操作,每种操作读入三个数
Ord=0
: 在
u,v
间连一条双向边
Ord=1
: 询问
u,v
最早在什么时候联通。
N,M <= 105
解题思路
对于维护联通块的问题,我们可以用并查集来维护,实际上并查集就是维护了一颗树,如果我们用按秩合并的话树的深度就是 O(LogN) 的。我们对于并查集上的边,加上一个边权,设为加入的时间,那么每次询问,就是询问 u,v 之间最大的边权,由于深度是 O(LogN) ,那么查询暴力跳也就是 O(LogN) 的了。
按秩合并
按秩合并是实际上就是对两个集合合并时的一种策略,对于一个集合,我们可以把他们抽象成一颗树,我们这棵树的最大深度是
Deep
,那么当我们合并两个这两个集合时我们分情况讨论。假设现在要合并集合
i
和集合
1.
Deepi>Deepj
:我们把
j
集合合并到
2.
Deepi=Deepj
:我们把
j
集合合并到
我们发现,只有在两个集合深度相同是新集合的深度才会加1,也就是说最坏情况就是类似二叉树的形态,所以说树的高度最大也只是
O(LogN)
的。
程序
//YxuanwKeith
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int MAXN = 5e5 + 5;
int N, M, Fa[MAXN], Num[MAXN], fa[MAXN], Time[MAXN], L[MAXN], R[MAXN];
int Get(int Now) {
if (Fa[Now] == Now) return Now;
Fa[Now] = Get(Fa[Now]);
return Fa[Now];
}
void Link(int x, int y, int time) {
int f1 = Get(x), f2 = Get(y);
if (f1 == f2) return;
if (Num[f1] > Num[f2]) swap(f1, f2);
Fa[f1] = f2, fa[f1] = f2, Time[f1] = time;
Num[f2] = max(Num[f2], Num[f1] + 1);
}
int Query(int x, int y) {
int f1 = Get(x), f2 = Get(y);
if (f1 != f2 || x == y) return 0;
L[0] = R[0] = 0;
for (; fa[x] != x; x = fa[x]) L[++ L[0]] = x; L[++ L[0]] = x;
for (; fa[y] != y; y = fa[y]) R[++ R[0]] = y; R[++ R[0]] = y;
for (; L[L[0]] == R[R[0]] && L[0] && R[0]; L[0] --, R[0] --);
int Ans = 0;
if (L[0]) Ans = max(Ans, Time[L[L[0]]]);
if (R[0]) Ans = max(Ans, Time[R[R[0]]]);
return Ans;
}
int main() {
scanf("%d%d", &N, &M);
int LastAns = 0, Cnt = 0;
for (int i = 1; i <= N; i ++) Fa[i] = i;
for (int i = 1; i <= M; i ++) {
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
y ^= LastAns, z ^= LastAns;
if (x == 0) Link(y, z, ++ Cnt); else {
LastAns = Query(y, z);
printf("%d\n", LastAns);
}
}
}