銀河英雄傳說

风掣凧浮發表於2024-11-25

[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\) 行,每行有一條指令。指令有兩種格式:

  1. M i j\(i\)\(j\) 是兩個整數(\(1 \le i,j \le 30000\)),表示指令涉及的戰艦編號。該指令是萊因哈特竊聽到的楊威利釋出的艦隊調動指令,並且保證第 \(i\) 號戰艦與第 \(j\) 號戰艦不在同一列。

  2. 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

flowchart subgraph 狀態2_整個艦隊大小為3 a_距離為1-->b_距離為1-->c_距離為0-->c_距離為0 end subgraph 狀態1 a_大小為1_距離為0-->a_大小為1_距離為0 b_大小為1_距離為0-->b_大小為1_距離為0 c_大小為1_距離為0-->c_大小為1_距離為0 end

對於兩個艦隊的合併,合併前如圖

flowchart subgraph 艦隊2_整個艦隊大小為3 d_距離為1-->e_距離為1-->f_距離為0-->f_距離為0 end subgraph 艦隊1_整個艦隊大小為3 a_距離為1-->b_距離為1-->c_距離為0-->c_距離為0 end

合併後,c的距離變為艦隊2的大小,其餘不變

flowchart subgraph 整個艦隊大小為6 a_距離為1-->b_距離為1-->c_距離為3 d_距離為1-->e_距離為1-->f_距離為0-->f_距離為0 end c_距離為3-->f_距離為0

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]\))

最終狀態如圖

flowchart a_距離為5-->f_距離為0 b_距離為4-->f_距離為0 c_距離為3-->f_距離為0 d_距離為2-->f_距離為0 e_距離為1-->f_距離為0 f_距離為0-->f_距離為0

程式碼

#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;
}

相關文章