基情链接♂BZOJ 1977 严格次小生成树——浅析最近公共祖先(LCA)
目录
一.题目:冷战
1.题目描述
1946 年 3 月 5 日,英国前首相温斯顿·丘吉尔在美国富尔顿发表“铁幕演说”,正式拉开了冷战序幕。美国和苏联同为世界上的“超级大国”,为了争夺世界霸权,两国及其盟国展开了数十年的斗争。在这段时期,虽然分歧和冲突严重,但双方都尽力避免世界范围的大规模战争(第三次世界大战)爆发,其对抗通常通过局部代理战争、科技和军备竞赛、太空竞争、外交竞争等“冷”方式进行,即“相互遏制,不动武力”,因此称之为“冷战”。Reddington 是美国的海军上将。由于战争局势十分紧张,因此他需要时刻关注着苏联的各个活动,避免使自己的国家陷入困境。苏联在全球拥有 N 个军工厂,但由于规划不当,一开始这些军工厂之间是不存在铁路的,为了使武器制造更快,苏联决定修建若干条道路使得某些军工厂联通。Reddington 得到了苏联的修建日程表,并且他需要时刻关注着某两个军工厂是否联通,以及最早在修建哪条道路时会联通。
具体而言,现在总共有M 个操作,操作分为两类:
• 0 u v,这次操作苏联会修建一条连接 u 号军工厂及 v 号军工厂的铁路,注意铁路都是双向的;
• 1 u v, Reddington 需要知道 u 号军工厂及 v 号军工厂最早在加入第几条条铁路后会联通,假如到这次操作都没有联通,则输出 0;
作为美国最强科学家, Reddington 需要你帮忙设计一个程序,能满足他的要求。
2.输入
第一行两个整数 N, M。
接下来 M 行,每行为 0 u v 或 1 u v 的形式。
数据是经过加密的,对于每次加边或询问,真正的 u, v 都等于读入的u, v 异或上上一次询问的答案。一开始这个值为 0。
1 ≤ N, M ≤ 500000,解密后的 u, v 满足1 ≤ u, v ≤ N, u不等于v
3.输出
对于每次 1 操作,输出 u, v 最早在加入哪条边后会联通,若到这个操作时还没联通,则输出 0。
4.样例输入
5 9
0 1 4
1 2 5
0 2 4
0 3 4
1 3 1
0 7 0
0 6 1
0 1 6
1 2 6
5.样例输出
0
3
5
二.题解
这道题第一眼看是并查集,对吧。但是不能直接用并查集做,因为要超时。我们可以将并查集和LCA联用起来。
首先因为我们只要两点相连通就行了。为避免成环,运用并查集判断连点是否连通。
又因为要找两个点最早多久连通,所以将i,即输入的序号当做权值,用一个数组val[i]存权值,表示点i到他父亲的权值,附权值就在启发式合并的时候进行就行了。注意:这里的并查集不能用路径压缩,因为我们后边要用fa[i]来爬山,所以要用启发式合并。
不用路径压缩找根结点:
int findSet (int x){
while (x != fa[x])
x = fa[x];
return x;
}
启发式合并+附边权:
void unionSet (int x, int y, int w){
int u = findSet (x), v = findSet (y);
if (u == v)
return ;
if (Rank[u] >= Rank[v]){
fa[v] = u;
val[v] = w;
if (Rank[u] == Rank[v])
Rank[u] ++;
}
else{
fa[u] = v;
val[u] = w;
}
}
然后在遇到要输出的时候,先用并查集判断两点是否在同一颗树上,如果在,就进入LCA函数;否则,输出0。首先将每个点的深度预处理一下,然后可以直接用暴力爬山法,先将两点爬到同一深度,再爬到LCA。爬的路程中爬一步比较一下最大权值,最后输出最大权值就行了。
深度预处理:
void prepare (int x){//dep[i]指深度
if (x == fa[x])
return ;
prepare (fa[x]);
dep[x] = dep[fa[x]] + 1;
}
是不是很简单?注意要异或!
三.代码
#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>
#include <vector>
using namespace std;
#define M 500005
int fa[M], n, m, Xor, dep[M], Rank[M], val[M], num;
void makeSet (int x){
for (int i = 0; i <= x; i ++)
fa[i] = i;
}
int findSet (int x){
while (x != fa[x])
x = fa[x];
return x;
}
void prepare (int x){
if (x == fa[x])
return ;
prepare (fa[x]);
dep[x] = dep[fa[x]] + 1;
}
void unionSet (int x, int y, int w){
int u = findSet (x), v = findSet (y);
if (u == v)
return ;
if (Rank[u] >= Rank[v]){
fa[v] = u;
val[v] = w;
if (Rank[u] == Rank[v])
Rank[u] ++;
}
else{
fa[u] = v;
val[u] = w;
}
}
int answer_question (int x, int y){
int Max = 0;
prepare (x);
prepare (y);
if (dep[x] < dep[y])
swap (x, y);
while (dep[x] > dep[y]){
Max = max (val[x], Max);
x = fa[x];
}
while (x != y){
Max = max (val[x], Max), Max = max (val[y], Max);
x = fa[x], y = fa[y];
}
return Max;
}
int main (){
scanf ("%d %d", &n, &m);
makeSet (n);
int u, v, flag;
for (int i = 1; i <= m; i ++){
scanf ("%d %d %d", &flag, &u, &v);
u = u ^ Xor;
v = v ^ Xor;
if (! flag){
num ++;
unionSet (u, v, num);
}
else{
int x = findSet (u), y = findSet (v);
if (x != y)
Xor = 0;
else
Xor = answer_question (u, v);
printf ("%d\n", Xor);
}
}
return 0;
}