您的位置:首页 > 其它

JZOJ4588. 【NOI2016模拟7.7】冷战 + 按秩合并讲解

2016-07-30 20:37 423 查看

题目大意

给定一副N个点的图。动态的往图中加边。现在有两种操作,每种操作读入三个数Ord,u,v:

Ord=0 : 在u,v间连一条双向边

Ord=1 : 询问u,v最早在什么时候联通。

N,M <= 105

解题思路

对于维护联通块的问题,我们可以用并查集来维护,实际上并查集就是维护了一颗树,如果我们用按秩合并的话树的深度就是O(LogN)的。我们对于并查集上的边,加上一个边权,设为加入的时间,那么每次询问,就是询问u,v之间最大的边权,由于深度是O(LogN),那么查询暴力跳也就是O(LogN)的了。

按秩合并

按秩合并是实际上就是对两个集合合并时的一种策略,对于一个集合,我们可以把他们抽象成一颗树,我们这棵树的最大深度是Deep,那么当我们合并两个这两个集合时我们分情况讨论。假设现在要合并集合i和集合j(Deepi>=Deepj)

1. Deepi>Deepj :我们把j集合合并到i集合,Deepi不变。

2. Deepi=Deepj :我们把j集合合并到i集合,Deepi=Deepi+1。

我们发现,只有在两个集合深度相同是新集合的深度才会加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);
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: