有一個劃分爲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;
}