【NOI2002】銀河英雄傳說 解題報告
阿新 • • 發佈:2018-11-28
這是一道邊帶權並查集的好題。(我的題解可能對不瞭解並查集的人不太友好)
具體分析
我們很容易想到記錄每隻船在某處排行位置,然後詢問時直接減一減就可以了。就這樣,問題轉換為如何保留每個數的位置(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; }