1. 程式人生 > >POJ 1182 ——食物鏈 經典並查集

POJ 1182 ——食物鏈 經典並查集

  • 題意 :有A,B,C三種動物構成環形的食物鏈,現給你M句話,每句話形式"D X Y",1表示X和Y同類,2表示X吃Y。判斷假話有多少句。其中 :1)與前面的話衝突 2)X和Y大於動物編號N 3)D =2 &&X =Y 是假話。
  • 思路 :這是一道經典的並查集題目。涉及到並查集,我們就針對這道題目設定並查集中各個元素的含義。 我們將可以確定關係的放在一個集合(即同類或者X 吃/被吃 Y)。rk[i]表示i與根節點的關係 : rk[i] = 0 表示i和根節點是同類 rk[i] = 1 表示根節點吃i rk[i] = 2 表示i吃根節點

一、初始化: node[i] = i , rk[i] = 0。 即每個節點都屬於獨立的集合, 二、find() 函式 :

除去 node[x] = find(node[x])外,每次遞迴還需要更新rk[x]的值。在沒有更新之前 rk[x]代表x和node[x]的關係,每次更新將rk[x]設定為x和根節點的關係。即t = node[x] rk[x] = (rk[x] + rk[t]) % 3。 這裡有兩個點需要解釋一下 1)t = node[x]的目的。find函式中有 node[x] = find(node[x]),如果不設定t = node[x],直接寫 rk[x] =(rk[x] + rk[node[x]])%3,執行結果會不一樣,因為t是當前x的父節點。例如樣例 : 在這裡插入圖片描述 2)rk[x] = (rk[x] + rk[t])%3。在此題中,已知x y z,假設x與y的關係 rk[x] = m, y與z的關係 rk[y] = n,則x與z的關係怎麼求?無外乎是3*3 = 9種情況。

rk[x] rk[y] x與z
0 0 0
0 1 1
0 2 2
1 0 1
1 1 2
1 2 0
2 0 2
2 1 0
2 2 1

如何得出上面的表格。不要忘了 只有三種動物A B C,並且關係是個環形。 例如 rk[x] = 1, rk[y] = 1,表示x被y吃,y被z吃,那麼從環形可以得知x與z的關係是2,即x吃z。 在這裡插入圖片描述 三、**merge函式 :**如果兩個節點不是一個集合,就合併。合併的時候fx = find(x), fy = find(y)。如果將node[fx] = fy,即將x所在樹作為y所在樹的子樹,則更新rk[fx]。 對於x和y之間的關係: 1)如果 d = 1,x與y的關係是同類,為0 2)如果 d = 2,x吃y,則x與y的關係是 1 所以x與y的關係是 d-1 rk[fx]更新為fx與fy的關係, 已知 fx 與 x的關係是(3-rk[x]),因為rk[x]是x與fx的關係,用3減去即反過來;x與y的關係是d-1,y與fy的關係是rk[y],所以rk[fx] = d-1 + 3-rk[x] + rk[y]。

四、如何判斷真假 : 每次輸入的d x y,如果是滿足三條規則的後兩條(x或者y大於n;x吃x)則是假話;如果find(x) = find(y),即x和y可以確定關係,則可以判斷 : 1)如果d = 1,rk[x] != rk[y],則是假話。因為 d = 1代表x和y是同類,與根節點的關係相同。 2)如果d = 2,(rk[x] + 1)%3 != rk[y],則是假話。因為d = 2,則x吃y, 當 rk[x] = 0時,rk[y] = 1 當 rk[x] = 1時,rk[y] = 2 當 rk[x] = 2時,rk[y] = 0 所以如果不滿足 x吃y,就是假話。

  • 程式碼 :
#include "cstdio"
#include "cmath"
#include "cstring"
#include "iostream"
using namespace std;
#define mem(a,b) memset(a,b,sizeof(a))
#define fori(i,l,u) for(int i = l;i < u;i++)
const int maxn = 1e6 + 6;
int node[maxn];
int rk[maxn];
int n,k;
int d,x,y;
int ans;
int fx,fy;
void init(){
    ans = 0;
    fori(i, 1, n+1) {      //初始化 rk[i]=0代表每個節點獨立
        node[i] = i;
        rk[i] = 0;
    }
}
int find(int x){
    int t = 0;
    if (x != node[x]) {
        t = node[x];
        node[x] = find(node[x]);
        cout<<"node[x] = "<<node[x]<<" t = "<<t<<endl;
        rk[x] = (rk[x] + rk[t]) % 3;  //通過 x與y的關係 y與z的關係,計算x與z的關係
    }
    return node[x];
}
void merge(int x,int y,int d){
    int fx = find(x);
    int fy = find(y);
    node[fx] = fy;
    rk[fx] = (d-1 + rk[y] + 3-rk[x])%3;
}
bool check(int d,int x,int y){
    bool a = true;
    if (x > n || y > n) {
        a = false;
    }
    if (d == 2 && (x == y)) {
        a = false;
    }
    return a;
}
int main()
{
//    freopen("1.txt", "r", stdin);
    scanf("%d%d",&n,&k);
    init();
    while (k--) {
        scanf("%d%d%d",&d,&x,&y);
        if (!check(d, x, y)) {
            ans ++;
        }
        else{
            fx = find(x);
            fy = find(y);
            if (fx == fy) {
                if (d == 1 && rk[x] != rk[y]) {
                    ans++;
                }
                if (d == 2 && (rk[y]+1)%3 != rk[x]) {
                    ans++;
                }
            }
            else{
                merge(x,y,d);
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}


複製
  • 遇到的問題 : 1)這道題用cin會TLE,我做的並查集的題一般資料大都用scanf。 2)沒有註釋 freopen。。。自閉了。 3)只有一組資料,不要跟hdu似的while ~scanf