题目
题目概要
N
N
N 个点、
M
M
M 次操作,要么是加入一条边,要么是询问两个点在哪次操作之后联通。
数据范围与约定
N
,
M
≤
5
×
1
0
5
N,M\le 5\times 10^5
N,M≤5×105 ,强制在线。
思路
考虑新增一条边带来的影响,本质是 将两个端点所属的连通块合并为一个连通块。
所以你可以看出,加入一条边的时候,如果两个端点已经连通,那么这条边不起任何作用。
询问“何时连通”,也就是在询问,什么时候两个连通块合并成了一个连通块。
我们用一棵树来表示一个连通块。节点既可以代表“点”,也可以代表“边”。对于一棵“连通块树”,叶子是“点”,其他的节点是“边”。这样一来,将两个连通块合并,等价于新增一个非叶子节点,代表这条新增的边,其子节点为两个端点所属的“连通块树”。如图。
此时,两个点的 l c a lca lca 就是连通的时间。
然后我们把这个过程稍稍优化。注意到 边的编号满足大根堆的性质。所以, l c a lca lca 的本质就是 两个点路径上最大的边编号。
既然如此,我们就可以稍稍的旋转一下。我们在插入的时候,也就是合并两个“连通块树”的时候,可以将其中一个的根节点作为新的根节点,而儿子是新加入的边。如图。
有意思的是,如果总是按照这个策略进行,一定有如下性质:
- 根节点是一个代表“点”的节点。(归纳法,最初的 n n n 个树都满足,新的根是原树的根)
- 任意一个代表“点”的节点,其子节点为“边”。(观察 A A A 的根可知)
- 任意一个代表“边”的节点,有且只有一个子节点,该子节点代表“点”。(观察 G G G 可知)
现在我们就可以稍微省略一些信息了。我们可以 将边权保存到其子节点上。
这样一来,“连通块树”就是一个完美的树,其中只包含带权的点。查询就是找路径上的最大值。
注意到现在的连边过程,相当于是将 a a a 的根与 b b b 的根相连。并查集?
启发式合并即可,因为不能路径压缩(我们要用到树上路径)。暴力爬山找路径上最大值——毕竟树高是 O ( log n ) \mathcal O(\log n) O(logn) 的。
代码
#include <cstdio>
#include <iostream>
#include <vector>
#include <cstdlib>
using namespace std;
inline int readint(){
int x; scanf("%d",&x); return x;
}
const int MaxN = 500005;
int h[MaxN]; // 按秩启发式合并
int v[MaxN]; // 与父亲的连边的编号
int fa[MaxN]; // 并查集中的父亲
inline int findSet(int a){
while(fa[a] != a) a = fa[a]; return a;
}
inline void unionSet(int a,int b,int id){
a = findSet(a), b = findSet(b);
if(a == b) return ;
if(h[a] < h[b]) swap(a,b);
if(h[a] == h[b]) ++ h[a];
fa[b] = a, v[b] = id;
}
bool vis[MaxN]; // 保持长期为全0
inline int query(int a,int b){
if(findSet(a) != findSet(b)) return 0;
for(int i=a; i!=fa[i]; i=fa[i])
vis[i] = true; // 注意根节点没有赋值哦!
int lca, res = -1;
for(lca=b; lca!=fa[lca]; lca=fa[lca])
if(vis[lca]) break;
for(int i=a; i!=lca; i=fa[i])
res = max(res,v[i]);
for(int i=b; i!=lca; i=fa[i])
res = max(res,v[i]);
for(int i=a; i!=fa[i]; i=fa[i])
vis[i] = false; // 复原
return res;
}
int main(){
int n = readint(), m = readint();
for(int i=1; i<=n; ++i) fa[i] = i;
int ans = 0, cntEdge = 0;
for(int opt,a,b; m; --m){
opt = readint();
a = readint()^ans;
b = readint()^ans;
if(opt == 0) unionSet(a,b,++cntEdge);
else printf("%d\n",ans=query(a,b));
}
return 0;
}