1. 程式人生 > >Onenjudge 百鍊 1703 ---- 發現它,抓住它 (並查集的應用)

Onenjudge 百鍊 1703 ---- 發現它,抓住它 (並查集的應用)

描述
一個城市中有兩個犯罪團伙A和B,你需要幫助警察判斷任意兩起案件是否是同一個犯罪團伙所為,警察所獲得的資訊是有限的。假設現在有N起案件(N<=100000),編號為1到N,每起案件由團伙A或團伙B所為。你將按時間順序獲得M條資訊(M<=100000),這些資訊分為兩類:
1、 D [a] [b]
其中[a]和[b]表示兩起案件的編號,這條資訊表明它們屬於不同的團伙所為

2、A [a] [b]
其中[a]和[b]表示兩起案件的編號,這條資訊需要你回答[a]和[b]是否是同一個團伙所為
注意你獲得資訊的時間是有先後順序的,在回答的時候只能根據已經接收到的資訊做出判斷。

輸入
第一行是測試資料的數量T(1<=T<=20)。
每組測試資料的第一行包括兩個數N和M,分別表示案件的數量和資訊的數量,其後M行表示按時間順序收到的M條資訊。

輸出
對於每條需要回答的資訊,你需要輸出一行答案。如果是同一個團伙所為,回答”In the same gang.”,如果不是,回答”In different gangs.”,如果不確定,回答”Not sure yet.”。

樣例輸入
1
5 5
A 1 2
D 1 2
A 1 2
D 2 4
A 1 4
樣例輸出
Not sure yet.
In different gangs.
In the same gang.

並查集和路徑壓縮演算法的應用。
1、 father[i] 表示 i 的父節點是誰; relation[i] 表示 i 這個節點和他父節點的關係,0表示同一個犯罪組織,1表示不同。
2、初始化將每個節點的父節點賦值為自身, relation 也理所當然的賦值為0(相同)。
3、 Find_father(int x) 函式返回值為 x 的祖先。採用路徑壓縮。由於是遞迴,途中不斷將這條路上上面節點接到祖先節點,並一路修改 relation 的值,直到最下面的節點接到祖先節點。
(1)如果 x 的父節點就是祖先節點,那麼 father[x] 的 r 為 0,x 的 r 不需要改變;
(2)否則,有幾種情況:
·r[ f[x] ] = 1,r[x] = 1,即 x 和他父親不同,x 父親和 x 父親的父親(祖先)也不同,所以 x 和 祖先相同
·r[ f[x] ] = 1,r[x] = 0,即 x 和他父親不同,x 父親和 x 父親的父親(祖先)相同,所以 x 和 祖先不同
·r[ f[x] ] = 0,r[x] = 1,即 x 和他父親相同,x 父親和 x 父親的父親(祖先)不同,所以 x 和 祖先不同
·r[ f[x] ] = 0,r[x] = 0,即 x 和他父親相同,x 父親和 x 父親的父親(祖先)也相同,所以 x 和 祖先相同
所以總結後有這樣一個公式: r[x] = (r[x] + r[ f[x] ]) % 2;
4、輸入命令時,如果是 D,表示這兩個案件是不同團伙所做,那麼把 x 的祖先歸併到 y 的祖先下面,並修改 relation的值。由於查詢祖先節點時,有路徑壓縮過程,所以歸併時 x 已經直接接到他的祖先節點上。
由於 x 和 y 的種類不同,考慮如下情況:
(1)若 y 和 y 的祖先節點 fy 相同, r[y] = 0;則 x 和 fy不同。若 r[x] = 1, x 和 fx 不同,則 fx 和 fy 相同,
即 r[fx] = 0;同理可得當 r[x] = 0,r[fx] = 1;所以 r[fx] = 1 - r[x];
(2)若 y 和 fy 不同,同上分析,可得 r[fx] = r[x].

記憶體: 7296kB
時間: 170ms
語言: G++
#include <iostream>
#include <cstdio>
using namespace std;

char cmd;
int T, N, M, num1, num2, fx, fy;
int father[100100], relation[100100];

int Find_father(int x)
{
    int temp = 0;
    if(x == father[x])
        return x;
    else//路徑壓縮
    {
        temp = Find_father(father[x]);//找到父節點的父節點--即祖先節點
relation[x] = (relation[father[x]] + relation[x]) % 2;//修改關係 father[x] = temp; } return father[x]; } void Union(int x,int y)//x和y是兩個不同的犯罪團伙 { fx = Find_father(x); fy = Find_father(y); father[fx] = fy;//將x那一支歸併到y的祖先節點下 if(relation[y] == 0)//y和fy相同 relation[fx] = 1-relation[x]; else relation[fx] = relation[x]; } int main() { scanf("%d",&T); while(T--) { scanf("%d%d",&N,&M);//N個案件,M條資訊 for(int i=1;i<=N;i++) { father[i] = i;//將父節點初始化為本身 relation[i] = 0; } while(M--) { getchar();//debug時發現如果不加這個讀回車,cmd就變成了回車。 scanf("%c%d%d",&cmd,&num1,&num2); if(cmd == 'D') Union(num1,num2); else if(cmd == 'A') { fx = Find_father(num1); fy = Find_father(num2); if(fx != fy)//不在並查集中 printf("Not sure yet.\n"); else if(relation[num1] == relation[num2])//與祖先節點關係相同 printf("In the same gang.\n"); else printf("In different gangs.\n"); } } } return 0; }