[NOI2002] 銀河英雄傳說
題目背景
公元 \(5801\) 年,地球居民遷至金牛座 \(\alpha\) 第二行星,在那裡發表銀河聯邦創立宣言,同年改元為宇宙曆元年,並開始向銀河系深處擴充。
宇宙歷 \(799\) 年,銀河系的兩大軍事集團在巴米利恩星域爆發戰爭。泰山壓頂集團派宇宙艦隊司令萊因哈特率領十萬餘艘戰艦出征,氣吞山河集團點名將楊威利組織麾下三萬艘戰艦迎敵。
題目描述
楊威利擅長排兵佈陣,巧妙運用各種戰術屢次以少勝多,難免恣生驕氣。在這次決戰中,他將巴米利恩星域戰場劃分成 \(30000\) 列,每列依次編號為 \(1, 2,\ldots ,30000\)。之後,他把自己的戰艦也依次編號為 \(1, 2, \ldots , 30000\),讓第 \(i\) 號戰艦處於第 \(i\) 列,形成“一字長蛇陣”,誘敵深入。這是初始陣形。當進犯之敵到達時,楊威利會多次釋出合併指令,將大部分戰艦集中在某幾列上,實施密集攻擊。合併指令為 M i j
,含義為第 \(i\) 號戰艦所在的整個戰艦佇列,作為一個整體(頭在前尾在後)接至第 \(j\) 號戰艦所在的戰艦佇列的尾部。顯然戰艦佇列是由處於同一列的一個或多個戰艦組成的。合併指令的執行結果會使佇列增大。
然而,老謀深算的萊因哈特早已在戰略上取得了主動。在交戰中,他可以透過龐大的情報網路隨時監聽楊威利的艦隊調動指令。
在楊威利釋出指令調動艦隊的同時,萊因哈特為了及時瞭解當前楊威利的戰艦分佈情況,也會發出一些詢問指令:C i j
。該指令意思是,詢問電腦,楊威利的第 \(i\) 號戰艦與第 \(j\) 號戰艦當前是否在同一列中,如果在同一列中,那麼它們之間佈置有多少戰艦。
作為一個資深的高階程式設計員,你被要求編寫程式分析楊威利的指令,以及回答萊因哈特的詢問。
輸入格式
第一行有一個整數 \(T\)(\(1 \le T \le 5 \times 10^5\)),表示總共有 \(T\) 條指令。
以下有 \(T\) 行,每行有一條指令。指令有兩種格式:
-
M i j
:\(i\) 和 \(j\) 是兩個整數(\(1 \le i,j \le 30000\)),表示指令涉及的戰艦編號。該指令是萊因哈特竊聽到的楊威利釋出的艦隊調動指令,並且保證第 \(i\) 號戰艦與第 \(j\) 號戰艦不在同一列。 -
C i j
:\(i\) 和 \(j\) 是兩個整數(\(1 \le i,j \le 30000\)),表示指令涉及的戰艦編號。該指令是萊因哈特釋出的詢問指令。
輸出格式
依次對輸入的每一條指令進行分析和處理:
- 如果是楊威利釋出的艦隊調動指令,則表示艦隊排列發生了變化,你的程式要注意到這一點,但是不要輸出任何資訊。
- 如果是萊因哈特釋出的詢問指令,你的程式要輸出一行,僅包含一個整數,表示在同一列上,第 \(i\) 號戰艦與第 \(j\) 號戰艦之間佈置的戰艦數目。如果第 \(i\) 號戰艦與第 \(j\) 號戰艦當前不在同一列上,則輸出 \(-1\)。
樣例 #1
樣例輸入 #1
4
M 2 3
C 1 2
M 2 4
C 4 2
樣例輸出 #1
-1
1
提示
樣例解釋
戰艦位置圖:表格中阿拉伯數字表示戰艦編號。
題目中沒有強制 \(i \neq j\),但是實測資料中不存在 \(i = j\) 的情況。
題解
本題目的是計算戰艦的距離,我們可以設定陣列\(sz[ ]\)作為集合的大小,\(dist[ ]\)作為距離(距離初始為0)。
然後在合併(函式merge)和尋找根節點(函式root)時,分部計算dist。
merge處合併時,原 根節點 在合併後改變距離,其餘部分不變
root處是計算此次查詢時,查詢資料與原根節點間各個元素改變的距離
具體計算過程如下
對於戰艦a,b,c(狀態1),merge處如果合併兩次,
由於只有原根節點 在合併後改變距離
merge(a,b)後 a距離變為b的大小(1) ,形成a-->b的艦隊,接著merge(b,c)後b距離變為c的大小(1),a不變,形成a-->b-->c的艦隊(狀態2)
這樣,合併後,艦隊除根節點的距離為0,其餘均為1
對於兩個艦隊的合併,合併前如圖
合併後,c的距離變為艦隊2的大小,其餘不變
mermaidmermaid接著,在root處\(dist[x] += dist[pre[x]]\)沿用上圖就一目瞭然了,
對於x=c,原式即為\(dist[c] += dist[f]\)結果為\(dist[c]=3+0=3\)
對於x=b,同理\(dist[b] += dist[c]\)(這裡\(dist[c]\)已更新為3)即為\(dist[b]=3+1=4\)
對於x=a,同b,\(dist[a]=4+1=5\) (4是\(dist[b]\))
最終狀態如圖
程式碼
#include <iostream>
using namespace std;
// n運算元量,x,y編號,pre每個戰艦父節點,dist到根節點距離,sz每個(根節點代表的)集合的大小
int n, x, y, pre[30010], dist[30010],sz[30010];
char ins;//指令
void merge(int x, int y);
void search(int x, int y);
int main()
{
// 初始化預處理(父節點pre,距離dist)
for (int i = 1; i <= 30000; i++) pre[i] = i,sz[i] = 1;
cin >> n;
while (n--)
{
cin >> ins >> x >> y;
if (ins == 'M') {merge(x, y);}//合併
else {search(x, y);}//查詢
}
return 0;
}
int root(int x) {
if(pre[x] == x) return x;// 如果x是根節點,返回x
// 否則,遞迴查詢x的根節點,在回溯時更新資料
int rx = root(pre[x]);// 找根
// 更新dist,在遞迴中pre[pre[pre[...]]]自內向外層層更新,計算dist[x]
dist[x] += dist[pre[x]];// 更新dist
pre[x] = rx;// 更新父節點,壓縮路徑
return rx;
}
void merge(int x, int y) {
int rx = root(x),ry = root(y);
if (rx == ry) return;// 若在同一列,則不用合併
pre[rx] = ry;// 合併,rx的父節點指向ry
dist[rx] = sz[ry];// 更新rx的dist
sz[ry] += sz[rx];// 更新ry的集合大小
}
void search(int x, int y) {
// 若是同一戰艦,直接輸出0
if (x == y) {cout << 0 << endl;return;}
// 如果在同一列,計算距離
if (root(x) == root(y))
cout << abs(dist[x] - dist[y])-1 << endl;
// 如果不在同一列,輸出-1
else cout << -1 << endl;
}