NOI2002 銀河英雄傳說

2022-05-08 20:03:10 字數 3662 閱讀 7280

公元五八○一年,地球居民遷至金牛座α第二行星,在那裡發表銀河聯邦創立宣言,同年改元為宇宙歷元年,並開始向銀河系深處拓展。

宇宙歷七九九年,銀河系的兩大軍事集*在巴公尺利恩星域爆發戰爭。泰山壓頂集**宇宙艦隊司令萊因哈特率領十萬餘艘戰艦出征,氣吞山河集*點名將楊威利組織麾下三萬艘戰艦迎敵。

楊威利擅長排兵布陣,巧妙運用各種戰術屢次以少勝多,難免恣生驕氣。在這次決戰中,他將巴公尺利恩星域戰場劃分成 3000030000 列,每列依次編號為 1, 2, …,300001,2,…,30000 。之後,他把自己的戰艦也依次編號為 1, 2, …, 300001,2,…,30000 ,讓第 ii 號戰艦處於第 ii 列 (i = 1, 2, …, 30000)(i=1,2,…,30000) ,形成「一字長蛇陣」,誘敵深入。這是初始陣形。當進犯之敵到達時,楊威利會多次發布合併指令,將大部分戰艦集中在某幾列上,實施密集攻擊。合併指令為$m_,含義為第i號戰艦所在的整個戰艦佇列,作為乙個整體(頭在前尾在後)接至第j號戰艦所在的戰艦佇列的尾部。顯然戰艦佇列是由處於同一列的乙個或多個戰艦組成的。合併指令的執行結果會使佇列增大。

然而,老謀深算的萊因哈特早已在戰略上取得了主動。在交戰中,他可以通過龐大的情報網路隨時監聽楊威利的艦隊調動指令。

在楊威利發布指令調動艦隊的同時,萊因哈特為了及時了解當前楊威利的戰艦分布情況,也會發出一些詢問指令: c_ci,j​。該指令意思是,詢問電腦,楊威利的第 ii 號戰艦與第 jj 號戰艦當前是否在同一列中,如果在同一列中,那麼它們之間布置有多少戰艦。

作為乙個資深的高階程式設計員,你被要求編寫程式分析楊威利的指令,以及回答萊因哈特的詢問。

最終的決戰已經展開,銀河的歷史又翻過了一頁……

輸入格式:

第一行有乙個整數 $t(1 \le t \le 500,000),表示總共有$ t$條指令。

以下有 tt 行,每行有一條指令。指令有兩種格式:

m_mi,j​: ii 和 jj 是兩個整數 (1 \le i,j \le 30000)(1≤i,j≤30000) ,表示指令涉及的戰艦編號。該指令是萊因哈特竊聽到的楊威利發布的艦隊調動指令,並且保證第 ii 號戰艦與第 jj 號戰艦不在同一列。

c_ci,j​: ii 和 jj 是兩個整數 (1 \le i,j \le 30000)(1≤i,j≤30000) ,表示指令涉及的戰艦編號。該指令是萊因哈特發布的詢問指令。

輸出格式:

依次對輸入的每一條指令進行分析和處理:

如果是楊威利發布的艦隊調動指令,則表示艦隊排列發生了變化,你的程式要注意到這一點,但是不要輸出任何資訊;

如果是萊因哈特發布的詢問指令,你的程式要輸出一行,僅包含乙個整數,表示在同一列上,第 ii 號戰艦與第 jj 號戰艦之間布置的戰艦數目。如果第 ii 號戰艦與第 jj 號戰艦當前不在同一列上,則輸出 -1−1 。

4

m 2 3

c 1 2

m 2 4

c 4 2

-1

1

先來分析一下這些指令的特點,很容易發現對於每個m指令,只可能一次移動整個佇列,並且是把兩個佇列首尾相接合併成乙個佇列,不會出現把乙個佇列分開的情況,因此,我們必須要找到乙個可以一次操作合併兩個佇列的方法。

再來看下c指令:判斷飛船i和飛船j是否在同一列,若在,則輸出它們中間隔了多少艘飛船。我們先只看判斷是否在同一列,由於每列一開始都只有一艘飛船,之後開始合併,結合剛剛分析過的m指令,很容易就想到要用並查集來實現。

定義乙個陣列fa,fa[i]表示飛船i的祖先節點,即其所在列的隊頭。再定義乙個用於查詢飛船祖先的函式find,在每次遞迴找祖先的同時更新fa,壓縮路徑,大大減小以後的時間消耗。初始時對於每個fa[i]都賦值為i,合併時就先分別查詢飛船i和飛船j的祖先,然後將飛船i的祖先的祖先(即fa[飛船i的祖先])賦值為飛船j的祖先。最後每次判斷時只需要找到飛船i和飛船j的祖先,判斷是否是同一艘飛船,若是,則在同一列,反之,則不在。

現在,判斷是否在同一列以及如何一次操作合併兩個佇列的問題已經解決,但還有問題需要解決:如何在以上方法的基礎上,進一步得到兩艘飛船之間的飛船數量呢?

我們先來分析一下:兩艘飛船之間的飛船數量,其實就是艘飛船之間的距離,那麼,這就轉換為了乙個求距離的問題。兩艘飛船都是在佇列裡的,最簡單的求距離的方法就是前後乙個乙個查詢,但這個方法太低效,會超時。看見多次求兩個點的距離的問題,便想到用字首和來實現:開乙個front陣列,front[i]表示飛船i到其所在佇列隊頭的距離,然後飛船i和飛船j之間的飛船數量即為它們到隊頭的距離之差減一,就是abs(front[i]-front[j])-1。

解決了如何高效得到兩艘飛船之間飛船數量的問題,便又發現了新的問題:如何在之前方法的基礎上,得到每艘飛船和隊頭的距離呢?

來分析一下現在已經使用的演算法——並查集,它的特點就是不是直接把乙個佇列裡的所有飛船移到另乙個佇列後面,而是通過將要移動的佇列的隊頭連線到另乙個佇列的隊頭上,從而間接連線兩個佇列。因此,我們在這個演算法的基礎上,每次只能更新一列中一艘飛船到隊頭的距離(如果更新多艘的話並查集就沒有意義了)。

那麼,該更新哪艘飛船呢?現在我們已經知道,使用並查集合並兩個佇列時只改變隊頭的祖先,而這個佇列裡其它飛船的祖先還是它原來的隊頭,並沒有更新,所以這個佇列裡的其它飛船在佇列合併之後,仍然可以找到它原來的隊頭,也就可以使用它原來隊頭的資料,因此,在每次合併的時候,只要更新合併前隊頭到目前隊頭的距離就可以了,之後其它的就可以利用它來算出自己到隊頭的距離。

理清了思路,但又有問題出現:該怎樣更新呢?該怎麼計算呢?

更新很容易,我們來分析一下:對於原來的隊頭,它到隊頭的距離為0,當將它所在的佇列移到另乙個佇列後面時,它到隊頭的距離就是排在它前面的飛船數,也就是合併前另乙個佇列的飛船數量。因此,就知道該怎樣實現了,我們再建乙個陣列num,num[i]表示以i為隊頭的佇列的飛船數量,初始時都是1,在每次合併的時候,fx為合併前飛船i的隊頭,fy為合併前飛船j的隊頭,每次合併時,先更新front[fx],即給它加上num[fy],然後開始合併,即fa[fx]=fy,最後更新num, num[fy]+= num[fx];num[fx]=0。

現在就差最後一步了:如何計算每個飛船到隊頭的距離。再來分析一下:對於任意乙個飛船,我們都知道它的祖先(不一定是隊頭,但一定間接或直接指向隊頭),還知道距離它祖先的距離。對於每乙個飛船,它到隊頭的距離,就等於它到它祖先的距離加上它祖先到隊頭的距離,而它的祖先到隊頭的距離,也可以變成類似的。可以遞迴實現,由於每一次更新都要用到已經更新完成的祖先到隊頭的距離,所以要先遞迴找到隊頭,然後在回溯的時候更新(front[i]+=front[fa[i]]),可以把這個過程和查詢隊頭的函式放在一起。

所以**:

#include#define ll long long

using namespace std;

int read()

while (isdigit(ch))

return x * f;

}inline void write(int x)

if(x<0)

while(x)ch[++f]=x%10+'0',x/=10;

while(f)putchar(ch[f--]);

putchar('\n');

}int fa[1100000],a[1100000],b[1100000];

int n,m;

int f(int x)

int main()

for(int i=1;i<=n;i++)

else if(x=='c')

{if(tx!=ty) cout<<"-1"《其實**並不長......

嗯~ o(* ̄▽ ̄*)o

NOI2002 銀河英雄傳說

noi2002 day1 t1 公元5801年,地球居民遷移至金牛座 第二行星,在那裡發表銀河聯邦創立宣言,同年改元為宇宙歷元年,並開始向銀河系深處拓展。宇宙歷七九九年,銀河系的兩大軍事集團在巴公尺利恩星域爆發戰爭。泰山壓頂集團派宇宙艦隊司令萊因哈特率領十萬餘艘戰艦出征,氣吞山河集團點名將楊威利組織...

NOI 2002 銀河英雄傳說

有 n 列戰場,每一列一開始只有乙個戰艦,編號就是對應的戰場編號。有 m 次操作 注意每一列戰場的戰艦都是排成一列的。帶偏移量的並查集。記錄 d x 表示 x 到所在列頭的一段上共有多少個戰艦。記錄 sz x 表示 x 所在的一列有多少個戰艦。為了保證複雜度,我們在做的時候還是要路徑壓縮。但是 d ...

NOI2002 銀河英雄傳說

公元五八 一年,地球居民遷移至金牛座 第二行星,在那裡發表銀河聯邦創立宣言,同年改元為宇宙歷元年,並開始向銀河系深處拓展。宇宙歷七九九年,銀河系的兩大軍事集 在巴公尺利恩星域爆發戰爭。泰山壓頂集 宇宙艦隊司令萊因哈特率領十萬餘艘戰艦出征,氣吞山河集 點名將楊威利組織麾下三萬艘戰艦迎敵。楊威利擅長排兵...