[NOI2002]銀河英雄傳說--帶權並查集

看原題戳這裏

題目描述

公元五八〇一年,地球居民遷至金牛座α第二行星,在那裏發表銀河聯邦創立宣言,同年改元爲宇宙曆元年,並開始向銀河系深處拓展。
宇宙歷七九九年,銀河系的兩大軍事集團在巴米利恩星域爆發戰爭。泰山壓頂集團派宇宙艦隊司令萊因哈特率領十萬餘艘戰艦出征,氣吞山河集團點名將楊威利組織麾下三萬艘戰艦迎敵。
楊威利擅長排兵佈陣,巧妙運用各種戰術屢次以少勝多,難免恣生驕氣。在這次決戰中,他將巴米利恩星域戰場劃分成30000列,每列依次編號爲1, 2, …,30000。之後,他把自己的戰艦也依次編號爲1, 2, …, 30000,讓第i號戰艦處於第i列(i = 1, 2, …, 30000),形成“一字長蛇陣”,誘敵深入。這是初始陣形。當進犯之敵到達時,楊威利會多次發佈合併指令,將大部分戰艦集中在某幾列上,實施密集攻擊。合併指令爲Mi,j,含義爲第i號戰艦所在的整個戰艦隊列,作爲一個整體(頭在前尾在後)接至第j號戰艦所在的戰艦隊列的尾部。顯然戰艦隊列是由處於同一列的一個或多個戰艦組成的。合併指令的執行結果會使隊列增大。
然而,老謀深算的萊因哈特早已在戰略上取得了主動。在交戰中,他可以通過龐大的情報網絡隨時監聽楊威利的艦隊調動指令。
在楊威利發佈指令調動艦隊的同時,萊因哈特爲了及時瞭解當前楊威利的戰艦分佈情況,也會發出一些詢問指令:C_{i,j} 。該指令意思是,詢問電腦,楊威利的第i號戰艦與第j號戰艦當前是否在同一列中,如果在同一列中,那麼它們之間佈置有多少戰艦。
作爲一個資深的高級程序設計員,你被要求編寫程序分析楊威利的指令,以及回答萊因哈特的詢問。
最終的決戰已經展開,銀河的歷史又翻過了一頁……

輸入格式

第一行有一個整數T(1≤T≤500,000),表示總共有T條指令。
以下有T行,每行有一條指令。指令有兩種格式:
M i,j:i,j是兩個整數(1≤i,j≤30000),表示指令涉及的戰艦編號。該指令是萊因哈特竊聽到的楊威利發佈的艦隊調動指令,並且保證第i號戰艦與第j號戰艦不在同一列。
C i,j:i,j是兩個整數(1≤i,j≤30000),表示指令涉及的戰艦編號。該指令是萊因哈特發佈的詢問指令。

輸出格式

依次對輸入的每一條指令進行分析和處理:
如果是楊威利發佈的艦隊調動指令,則表示艦隊排列發生了變化,你的程序要注意到這一點,但是不要輸出任何信息;
如果是萊因哈特發佈的詢問指令,你的程序要輸出一行,僅包含一個整數,表示在同一列上,第i號戰艦與第j號戰艦之間佈置的戰艦數目。如果第i號戰艦與第j號戰艦當前不在同一列上,則輸出−1。

輸入輸出樣例

輸入

4
M 2 3
C 1 2
M 2 4
C 4 2

輸出

-1
1

說明/提示 【樣例說明】

戰艦位置圖:表格中阿拉伯數字表示戰艦編號
在這裏插入圖片描述

思路

初見這道題,首先想到的方法當然是直接模擬,模擬每一次指令。當然這種方法對於小數據行得通,但對於此題的500,000個指令,肯定超時。
因此我們就要想其它方法。
先來分析一下這些指令的特點,很容易發現對於每個M指令,只可能一次移動整個隊列,並且是把兩個隊列首尾相接合併成一個隊列,不會出現把一個隊列分開的情況,因此,我們必須要找到一個可以一次操作合併兩個隊列的方法。
再來看下C指令:判斷飛船i和飛船j是否在同一列,若在,則輸出它們中間隔了多少艘飛船。我們先只看判斷是否在同一列,由於每列一開始都只有一艘飛船,之後開始合併,結合剛剛分析過的M指令,很容易就想到要用並查集來實現。
但一般的並查集只具有查找功能,而這裏要用一個可以進行數值操作的,因此要用帶權並查集。
帶權並查集,就是在合併並查集時,將其全權值同時進行操作。
對於每個點,分別記錄所屬鏈的頭結點、該點到頭結點的距離以及它所在集合的大小。
每次合併將y接在x的尾部,改變y頭的權值和所屬鏈的頭結點,同時改變x的尾節點。
注意:每次查找的時候也要維護每個節點的權值。
每次查詢時計算兩點的權值差。
看代碼,有批註的

#include<iostream>
#include<cmath>
using namespace std;
int father[30001],s[30001],length[30001];
int find(int t)//查找
{
    if(father[t]==t) return t;
    int k=father[t];
    father[t]=find(father[t]);//路徑壓縮
    s[t]+=s[k];//更新當前節點到根的距離
    length[t]=length[father[t]];//更新所在集合大小
    return father[t];
}
int main()
{
//	freopen("galaxy.in","r",stdin);
//	freopen("galaxy.out","w",stdout);
	
    int n;
    cin>>n;
    for(int i=1;i<=30000;i++) 
	{
		father[i]=i;
		s[i]=0;
		length[i]=1;
	}
    for(int i=1;i<=n;i++)
    {
        char pic;
        int x,y,dx,dy;
        cin>>pic>>x>>y;
        if(pic=='M')
        {
            dx=find(x);//查找x的根
            dy=find(y);//查找y的根
            father[dx]=dy;//把x放在y後面
            s[dx]+=length[dy];//更新x的根到新的根的距離
            length[dx]+=length[dy];//更新集合大小
            length[dy]=length[dx];//更新集合大小
        }
        if(pic=='C')
        {
            dx=find(x);
            dy=find(y);
            if(dx!=dy)cout<<-1<<endl;//不在同一個集合中
            else cout<<abs(s[x]-s[y])-1<<endl;
            //中間戰艦的數量等於x到根的距離減y到根的距離減一。
        }
    }
    return 0;
}
發佈了11 篇原創文章 · 獲贊 5 · 訪問量 1420
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章