複製鏈接
請複製以下鏈接發送給好友

銀河英雄傳説

(編程題)

鎖定
“銀河英雄傳説”是NOI 2002第一試試題(編程題)。
中文名
銀河英雄傳説
外文名
Ginga Eiyudensetsu

銀河英雄傳説題目

銀河英雄傳説問題描述

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

銀河英雄傳説輸入文件

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

銀河英雄傳説輸出文件

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

銀河英雄傳説樣例輸入

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

銀河英雄傳説樣例輸出

-1
1

銀河英雄傳説樣例説明

戰艦位置圖:表格中阿拉伯數字表示戰艦編號
-
第一列
第二列
第三列
第四列
……
初始時
1
2
3
4
……
M 2 3
1
-
3
2
4
……
C 1 2
1 號戰艦與 2 號戰艦不在同一列,因此輸出-1
M 2 4
1
-
-
4
3
2
……
C 4 2
4 號戰艦與 2 號戰艦之間僅佈置了一艘戰艦,編號為 3,輸出 1

銀河英雄傳説題目分析

本題是並查集的典型應用。
由於數據量不是很大,而且題目要求第i號戰艦所在的整個戰艦隊列,作為一個整體(頭在前尾在後)接至第j號戰艦所在的戰艦隊列的尾部。所以我們應該使用並查集的普通查找和合並的算法,即不能進行路徑壓縮,也不是按大小求並。
此外,普通的並查集的根結點存儲的是整棵樹的大小的相反數,而本題目要求知道每列艦隊的隊尾序號,所以在本題中要設根結點的值為本隊列隊尾序號的相反數,每個結點初始化為自身序號的相反數。
尋找根結點的算法與普通並查集的算法相同,但合併算法略有區別:
題目要求將第posI號戰艦所在的整個戰艦隊列,作為一個整體(頭在前尾在後)接至第posJ號戰艦所在的戰艦隊列的尾部,所以我們必須先分別找到posI和posJ的根結點fI和fJ,然後找到fJ的尾結點rear = -father[fJ],再令father[fJ] = father[fI],把fI的尾結點作為fJ的尾結點,最後令father[fI] = rear,把fI接在fJ的隊尾,完成合並操作。
至於查詢指令,我們先要找到posI的根結點fI,然後找到fJ的尾結點rear = -father[fJ],再從rear開始往上查找posI和posJ。
需要注意的是posI不一定在posJ的後面,所以我們有可能先找到posJ,如果是這樣的話我們修改posJ的值,使其等於posI,然後繼續尋找posJ,直到遍歷完整棵樹。

銀河英雄傳説C++代碼

#include<iostream>
using namespace std;
int FindFather(int father[], int pos);
void Unite(int father[], int posI, int posJ);
int Search(int father[], int posI, int posJ);
int main()
{
    const int MAX = 30000; 
    int *father = new int[MAX+1];
    for (int i=0; i<=MAX; i++) //所有的數組元素值均初始化為自身序號的相反數 
        father[i] = -i;
        
    int T;
    cin >> T;
    
    char ch;
    int  posI, posJ;
    
    for (int i=0; i<T; i++)
    {
        cin >> ch;
        cin >> posI >> posJ;
        
        if (ch == 'M')
            Unite(father, posI, posJ);
        else
            cout << Search(father, posI, posJ) << endl;
    }
     
    delete []father;
    system("pause");
    return 0;
}
//尋找第i號戰艦所在的整個戰艦隊列的對頭 
int FindFather(int father[], int pos)
{
    if (father[pos] < 0)
        return pos;
        
    return FindFather(father, father[pos]);
}
//合併指令 
void Unite(int father[], int posI, int posJ)
{
    //首先各自去尋找所在的整個戰艦隊列的對頭
    int fI = FindFather(father, posI); 
    int fJ = FindFather(father, posJ);
    
    //第i號戰艦所在的整個戰艦隊列,作為一個整體(頭在前尾在後)接至第j號戰艦所在的戰艦隊列的尾部
    int rear = -father[fJ]; //posJ所在隊列的隊尾
    father[fJ] = father[fI]; 
    father[fI] = rear; 
}
//查詢指令 
int Search(int father[], int posI, int posJ)
{
    int fI = FindFather(father, posI); //posI所在隊列的隊頭
    int rear = -father[fI];           //posI所在隊列的隊尾
    
    while (rear != posI && rear != posJ) //尋找posI或posJ在隊列中的位置 
        rear = father[rear];
    
    if (rear == posJ)//如果先找到posJ,則設posJ = posI,繼續尋找posJ 
        posJ = posI;
        
    int len = -1;
    while (rear != father[fI] && rear != posJ)//尋找posJ在隊列中的位置
    {
        len++;
        rear = father[rear];
    }
    
    //如果第i號戰艦與第j號戰艦當前不在同一列上,則輸出-1
    if (rear != posJ)
        len = -1;
    
    return len;
}

銀河英雄傳説Pascal

PROGRAM P1034 (INPUT, OUTPUT);
CONST
    MAX = 5000;
VAR
    father : ARRAY [1..MAX] OF INTEGER;
    T, posI, posJ, i : integer;
    ch : char;
{尋找第i號戰艦所在的整個戰艦隊列的對頭}
FUNCTION FindFather(pos : integer): integer;
    begin
        if father[pos] < 0 then
            FindFather := pos
        else
            FindFather := FindFather(father[pos]);
    end; {FindFather}
{合併指令}
PROCEDURE Unite(posI, posJ : integer);
    var
        fI, fJ, rear : integer;
    begin
        {首先各自去尋找所在的整個戰艦隊列的對頭}
        fI := FindFather(posI);
        fJ := FindFather(posJ);
        {第i號戰艦所在的整個戰艦隊列,作為一個整體(頭在前尾在後)接至第j號戰艦所在的戰艦隊列的尾部}
        rear := -father[fJ]; {posJ所在隊列的隊尾}
        father[fJ] := father[fI];
        father[fI] := rear;
    end; {Unite}
{查詢指令}
FUNCTION Search(posI, posJ : integer): integer;
    var
        fI, rear, len : integer;
    begin
        fI := FindFather(posI); {posI所在隊列的隊頭}
        rear := -father[fI];    {posI所在隊列的隊尾}
        while (rear <> posI) and (rear <> posJ) do {尋找posI或posJ在隊列中的位置}
            rear := father[rear];
        if rear = posJ then {如果先找到posJ,則設posJ = posI,繼續尋找posJ}
            posJ := posI;
        len := -1;
        while (rear <> father[fI]) and (rear <> posJ) do {尋找posJ在隊列中的位置}
        begin
            inc(len);
            rear := father[rear];
        end; {while}
        {如果第i號戰艦與第j號戰艦當前不在同一列上,則輸出-1}
        if rear <> posJ then
            len := -1;
        Search := len;
    end; {Search}
BEGIN {MAIN}
    for i:=1 to MAX do {所有的數組元素值均初始化為自身序號的相反數}
        father[i] := -i;
    readln(T);
    for i:=1 to T do
    begin
        read(ch);
        read(posI);
        readln(posJ);
        if ch = 'M' then
            Unite(posI, posJ)
        else
            writeln(Search(posI, posJ));
    end; {for}
END.