题目:BZOJ4668 冷战
时间/内存限制:
题目描述:
S国在全球拥有N个军工厂,但由于规划不当,一开始这些军工厂之间是不存在铁路的,为了使武器制造更快,S国决定修建若干条道路使得某些军工厂联通。 A国得到了S国的修建日程表,并且需要时刻关注着某两个军工厂是否联通,以及最早在修建哪条道路时会联通。具体而言,现在总共有M个操作,操作分为两类: 0 u v,这次操作S国会修建一条连接u号军工厂及v号军工厂的双向铁路; 1 u v,A国需要知道u号军工厂及v号军工厂最早在加入第几条铁路后会联通,假如到这次操作都没有联通,则输出0;
输入
输入第一行为两个整数N和M(1≤N,M≤500 000)。 接下来M行,每行为0 u v或1 u v的形式。 数据是经过加密的,对于每次加边或询问,真正的u,v都等于读入的u, v异或 上一次询问的答案。一开始这个值为0。 解密后的u,v满足1≤u,v≤N,u不等于v。
输出
对于每次1的操作,输出u,v最早在加入哪条边后会联通,若到这个操作时还没联通,则输出 0。
样例输入
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
样例输出
0
3
5
代码
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
const int NUM = 5e5+10;
int N,M;
int p[NUM];
std::vector<std::vector<int>> jilu;
int findd(int x){ //找根节点
if(p[x] != x){
p[x] = findd(p[x]);
}
return p[x];
}
int main(){
cin >> N >> M;
// 初始化 jilu
jilu.resize(M + 1);
for(int i=1;i<=N;i++){
p[i] = i;
jilu[0].push_back(i);
}
int k = 0;
int edgnum = 0;
for(int i=1;i<=M;i++){
int opcode,u,v;
cin >> opcode >> u >> v;
u = k ^ u;
v = k ^ v;
if(opcode == 0){
edgnum ++;
p[findd(u)] = findd(v);
for(int node=1;node<=N;node++){
jilu[edgnum].push_back(findd(node));
}
}
else{
if(findd(u) == findd(v)){
for(int edge=1;edge<=edgnum;edge++){
if(jilu[edge][u - 1] == jilu[edge][v - 1]){
cout << edge << endl;
k = edge;
break;
}
}
}
else{
cout << 0 << endl;
k = 0;
}
}
}
return 0;
}
结果
超内存了,待修改
代码(修改)
#include<iostream>
using namespace std;
const int MaxN = 500010;
int N,M;
int p[MaxN];
int deep[MaxN];
int timee[MaxN], edge, rankk[MaxN];
int findd(int x){
if(x == p[x]){
return x;
}
int f = findd(p[x]);
deep[x] = deep[p[x]] + 1;
return f;
}
void merge(int a, int b){
edge ++;
int x = findd(a);
int y = findd(b);
if(x == y){
return;
}
if(rankk[x] > rankk[y]){
p[y] = x;
timee[y] = edge;
}
else{
if(rankk[x] == rankk[y]){
rankk[y] ++;
}
p[x] = y;
timee[x] = edge;
}
return;
}
int query(int u, int v){
int res = 0;
int x = findd(u);
int y = findd(v);
if(x != y){
return 0;
}
while(u != v){
if(deep[u] < deep[v]){
swap(u, v);
}
res = max(res, timee[u]);
u = p[u];
}
return res;
}
int main(){
scanf("%d%d", &N,&M);
for(int i=1;i<=N;i++){
p[i] = i;
}
int k = 0;
while(M--){
int opcode,u,v;
scanf("%d%d%d",&opcode,&u,&v);
u ^= k;
v ^= k;
if(opcode == 0){
merge(u, v);
}
else{
k = query(u, v);
printf("%d\n", k);
}
}
return 0;
}
结果
思考
之前一直想不明白为啥要求公共祖先,直接输出两个顶点之间大的那个所用时间不就行了。后来发现如果是两个顶点来自两棵不同的树,如下图:
查询9和6的连接时间的时候,9保存的是9加入左树的时间,6保存的是6加入右树的时间。而这俩时间9和6还没连通,他俩联通的时间应该是左树与右树合并的时间,因此,这时需要向上找公共祖先保存的时间。
代码参考:
https://blog.csdn.net/sdz20172133/article/details/89077686