传送门:点击打开链接
题意:多次操作,操作1给两点之间增加一条边并求当前连通块数量,操作2求两点最早连通的时间,强制在线。
思路:q巨虐我千百遍
第一个思路想到可持久并查集+二分,因为很容易想到,然后只过了small的,large的T了
其实这题的思路比可持久并查集更简单。
并查集有两种方法来保持复杂度不退化,一种是路径压缩,一种是按照秩来做启发式合并,一般情况下我们都是写第一种
这里我们用第二种方法来合并,来保证树的形态不会被打乱。
每次查询两个点最早的连通时间,其实就是用最朴素的暴力方法,u和v都向上提找到LCA,然后LCA到u和LCA到v这两条链上的合并时间戳取最大值。
因为我们是按照启发式合并的,所以链长不会超过logn。
因为越靠近根节点的边越晚合并,所以只要看LCA连着的两条边的时间戳的最大值就够了。
#include<map>
#include<set>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<cstdio>
#include<cctype>
#include<string>
#include<vector>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<algorithm>
#include<functional>
#define fuck(x) cout<<"["<<x<<"]"
#define FIN freopen("input.txt","r",stdin)
#define FOUT freopen("output.txt","w+",stdout)
using namespace std;
typedef long long LL;
typedef pair<LL, int>PII;
const int MX = 1e6 + 5;
const int mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
int n, m;
int deep[MX], P[MX];
int vis[MX], A[MX], z[MX];
int find(int x) {
while(P[x] != x) x = P[x];
return x;
}
bool Union(int i, int u, int v) {
u = find(u), v = find(v);
if(u == v) return false;
if(deep[u] > deep[v]) swap(u, v);
P[u] = v; A[u] = i;
if(deep[u] == deep[v]) deep[v]++;
return true;
}
int Query(int i, int u, int v) {
vis[u] = i; z[u] = 0;
for(; u != P[u]; u = P[u]) {
vis[P[u]] = i;
z[P[u]] = A[u];
}
if(vis[v] == i) return z[v];
for(; v != P[v]; v = P[v]) {
if(vis[P[v]] == i) return max(z[P[v]], A[v]);
}
return 0;
}
int main() {
//FIN;
int T; scanf("%d", &T);
while(T--) {
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) {
P[i] = i; deep[i] = 1;
vis[i] = A[i] = z[i] = 0;
}
int ans = 0, tot = n, op, a, b;
for(int i = 1; i <= m; i++) {
scanf("%d%d%d", &op, &a, &b);
a ^= ans; b ^= ans;
if(op == 0) {
if(Union(i, a, b)) tot--;
ans = tot;
} else ans = Query(i, a, b);
printf("%d\n", ans);
}
}
return 0;
}