1. 程式人生 > >【NOI2002】銀河英雄傳說 解題報告

【NOI2002】銀河英雄傳說 解題報告

這是一道邊帶權並查集的好題。(我的題解可能對不瞭解並查集的人不太友好)

具體分析

我們很容易想到記錄每隻船在某處排行位置,然後詢問時直接減一減就可以了。就這樣,問題轉換為如何保留每個數的位置(d[i])。

Hint:注意,為了方便,我們把d[i]記作相對f[i]的位置。

當一列船(記為A 首隻船為 a)合併到另一列(記為B 首隻船為b)時,我們為了讓a直接以b為父親,我們要記錄每一列船的只數(s[i]),將d[a]修改為s[a] + 1(連到最後時rank為原來船數+1),別忘了修改s[b]與f[a]的值。對於後面父親不為B的先不管。當尋找祖先時,對於父親是祖先的船,我們不用修改,因為在合併時已經修改過了,而對於父親不是祖先的船,我們可以先修改它的父親(遞迴進行),是他的父親的父親為它的老祖宗,然後把它的d[i] = d[i] + d[f[i]];這樣就把參照物改為他的祖宗,實現了路徑壓縮。

程式碼很短,只有一點點。。。

程式碼

#include<bits/stdc++.h>
using namespace std;
#define N 30000

int T;
int f[N + 5], d[N + 5], s[N + 5];

int find( int x ){
    if ( f[x] == x ) return x;
    int fa(find(f[x]));
    d[x] = d[x] + d[f[x]] - 1;
    return f[x] = fa;
}

void Merge( int x, int y ){
    x = find(x); y = find(y);
    if ( x == y ) return;
    f[x] = y; d[x] = s[y] + 1; s[y] += s[x];
}

int main(){
    scanf( "%d", &T );
    for ( int i = 1; i <= N; ++i ) f[i] = i, d[i] = 1, s[i] = 1;
    while( T-- ){
        char t; int x, y;
        while( ( t = getchar() ) != 'M' && t != 'C' );
        scanf( "%d%d", &x, &y );
        if ( t == 'M' ) Merge( x, y );
        else{
            if ( find(x) == find(y) ){
                if ( x == y ) printf("0\n");
                else if ( d[x] > d[y] ) printf( "%d\n", d[x] - d[y] - 1 );
                else printf( "%d\n", d[y] - d[x] - 1 );
            } else printf( "-1\n" );
        }
    }
    return 0;
}