[NOI2002]銀河英雄傳說

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章