有一个划分为N列的星际战场, 各列依次编号为 1, 2, ···, N. 有 N 艘战舰, 也依次编号为 1 - N, 初始时, 第 i 号战舰在第 i 列.
T条指令, 有两种格式.
1. M i j, 表示让第 i 号战舰所在列的全部战舰保持原有的顺序, 接在第 j 号战舰所在列的尾部.
2. C i j, 表示询问第 i 号战舰与第 j 号战舰是否处在同一列中, 如果在同一列中, 他们之间间隔了多少艘战舰.
N <= 30000, T <= 5e5.
这道题很像是银行窗口排队, 要用到边带权的并查集, 我最开始看到这道题的时候, 当时不是很理解find的递归...后来就好点了.
其实我感觉迭代版的更好理解.
我们要用并查集维护一个链, 选出链头作为代表元.
size[i] 代表 i这条链上的节点的数量, 只在 i 是代表元时才有意义;
faz[i] 代表 i 的 父亲节点;
data[i] 代表 i 到 faz[i] 的边的数量, 即距离.
因为我们要进行路径压缩, 所以find函数和merge函数要与平常的做些修改.
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <stack>
#include <set>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cmath>
const int MAXN = 30600;
using namespace std;
int size[MAXN], faz[MAXN], data[MAXN];
void init(){
for(int i = 0; i < MAXN; ++i){
size[i] = 1;// 只有根节点的 size 才有意义
data[i] = 0;
faz[i] = i;
}
}
int find(int x){// 递归
if(x == faz[x]){
return x;
}
int root = find(faz[x]);
data[x] += data[faz[x]];
return faz[x] = root;
}
int find(int x){// 迭代
int root = x, sum = 0;
while(root != faz[root]){
sum += data[root];
root = faz[root];
}
int father, weight;
while(x != root){
father = faz[x];
weight = data[x];
data[x] = sum;
faz[x] = root;
sum -= weight;
x = father;
}
return root;
}
void merge(int x, int y){
x = find(x), y = find(y);
faz[x] = y;
data[x] = size[y];
size[y] += size[x];
}
int main(){
init();
int t;
scanf("%d", &t);
int x, y;
char cmd;
while(t--){
getchar();
scanf("%c%d%d", &cmd, &x, &y);
if(cmd == 'M'){
merge(x, y);
}
else{
if(find(x) == find(y)){
printf("%d\n", abs(data[x] - data[y]) - 1);
}
else{
printf("-1\n");
}
}
}
return 0;
}