1. 程式人生 > >洛谷P1196[NOI2002]銀河英雄傳說-並查集擴展

洛谷P1196[NOI2002]銀河英雄傳說-並查集擴展

銀河 初始 ring ref 之間 出現 分析 find 簡單

銀河英雄傳說

題意:在並查集的基礎上,還要求出同一集合的兩個點的距離

這道題用並查集自己是知道的,但是竟然可以這麽騷的操作。

下面轉自大佬的查詳細題解

初見這道題,首先想到的方法當然是直接模擬,模擬每一次指令。當然這種方法對於小數據行得通,但對於此題的500,000個指令,肯定超時。

因此我們就要想其它方法。

先來分析一下這些指令的特點,很容易發現對於每個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,在每次合並的時候,px為合並前飛船i的隊頭,py為合並前飛船j的隊頭,每次合並時,先更新front[px],即給它加上num[py],然後開始合並,即fa[px]=py,最後更新num, num[py]+= num[px];num[px]=0。

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

下面是我的ac代碼

#include <iostream>
#include <string>
#include <cstdio>
#include <algorithm>
const int maxn = 30000+5;
using namespace std;

int front[maxn],num[maxn],fa[maxn];
string s;

void init(){
    for(int i=1;i<maxn;i++)
    {
        fa[i] = i;
        num[i] =1;                //表示這個團體的個數        
        front[i] = 0;            //表示和頭節點的距離
    }
}
int find(int x)
{
      if(fa[x]==x)return x;
    int fn = find(fa[x]);
    front[x] += front[fa[x]]; //在回溯的時候更新front(因為更新時要用到正確的front[祖先],所以只能在回溯的時候更新)         
    return fa[x] = fn;
}
void uni(int x,int y)
{
    int px = find (x);
    int py = find (y);
    if(px==py)return;
    else
    {
        fa[px] = py;            //將py設為px的祖先 
        front[px] += num[py];    //更新front[x所在列隊頭(現在在y所在隊列後面)]即加上y所在隊列的長度 
        num[py] += num[px];        //更新以py為隊頭隊列的長度 
        num[px] = 0;            //以px為隊頭的隊列已不存在,更新 
    }    
}
int main(){
    int T;
    scanf("%d",&T);
    init();
    while(T--)
    {
        int u,v;
        cin>>s>>u>>v;
        if(s[0]==M)
        {    
            uni(u,v);
        }    
        else
        {
            int px = find (u);
            int py = find (v);
            if(px != py)printf("-1\n");
            else
            {
                int tmp = front[u] - front[v];        //
                if(tmp<0)tmp=-tmp;                    //取兩個點的絕對值再減去1;
                printf("%d\n",tmp-1);                //
            }
        }
    }
    return 0;
}

洛谷P1196[NOI2002]銀河英雄傳說-並查集擴展