帶權並查集!!!

普通的並查集僅僅記錄的是集合的關係,這個關係無非是同屬一個集合或者是不在一個集合。而帶權並查集,不僅記錄集合的關係,還記錄着集合內元素的關係或者說是元素連接線的權值。這裏用三個例題講解一下吧。

How Many Answers Are Wrong (HDU - 3038)

題目大意:
給你一系列區間和,判斷給出的區間中有幾個是不合法的。
思考:
1.如何建立區間之間的聯繫
2.如何發現悖論
首先是如何建立聯繫,我們可以用一張圖表示
這裏寫圖片描述
假如說區間【fx,x】是之前建立的區間,他們之間和爲sum[x],fx和x的聯繫可以用集合來存儲,同理【fy,y】也是如此。當給出了一個新的區間【x,y】時,且區間和爲s。就產生了兩種情況了,如果fx == fy 那麼這兩個區間是有關聯的區間,也就是【x,y】之間的和是可以求出的。可以把這個圖看成一個向量。 區間【x,y】的和就是可以寫成sum[x] - sum[y]。判斷給出的s與向量法計算的區間和是否相等就可以判斷是否是悖論。
當然如果fx != fy就需要建議新的區間關係。首先將fy指向fx,這代表fx是區間的左端點,計算sum【fy】= sum【x】- sum【y】+ s;這裏同樣用的是向量法。
這樣建立聯繫與判斷悖論都可以表達了,接下來就是一些細節了,比如在更新區間的時候要進行路徑的壓縮,壓縮的過程中需要對權值進行更新,目的是使每個已知區間最大化。
代碼

#include <cstdio>

const int maxn = 200000 + 10;

int pre[maxn];
int sum[maxn];    // sum是該節點到其根的和,比如說sum[3],3的根是1,那麼sum[3]表示的就是1到3的和……

int find(int x) {
    if (x == pre[x])  return x;
    else {
        int root = find(pre[x]);     // 找到根節點
        sum[x] += sum[pre[x]];       // 權值合併,更新
        return pre[x] = root;        // 壓縮路徑
    }
}

int main() {
    int n, m;
    while(~scanf("%d%d", &n, &m)) {
        for(int i = 0; i <= n; i++) {
            pre[i] = i;
            sum[i] = 0;
        }
        int x, y;
        int s;
        int ans = 0;
        while(m--) {
            scanf("%d%d%d", &x, &y, &s);
            x--;        // 想一下爲什麼要減一,可以讓類似【1,5】【6,10】這樣的區間可以合併……
            int fx = find(x);
            int fy = find(y);
            if (fx != fy) {
                pre[fy] = fx;
                sum[fy] = sum[x] - sum[y] + s;
            }
            else if (sum[y] - sum[x] != s)  ans++;


        }
        printf("%d\n", ans);
    }
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

有了這道題的基礎我們可以將帶權並查集升級成種類並查集

比如所這道題

A Bug’s Life POJ - 2492

每次給出兩個昆蟲的關係(異性關係),然後發現這些條件中是否有悖論
就比如說第一組數據

1 2
2 3
1 3
1和2是異性,2和3是異性,然後說1和3是異性就顯然不對了。

我們同樣可以思考一下這道題如何用帶權並查集去做。
這裏寫圖片描述
首先用r[x]存儲的是x與其根節點rx的關係,0代表同性1代表異性(其實反着也一樣因爲這個關係是一個環狀的)
這道題與上一道題唯一的不同是權值不是累加的關係而是相當於二進制的個位,也就是累加結果取%2。
這樣就很容易仿照上一道題寫出一下代碼

#include <cstdio>

const int maxn = 2000 + 10;

int pre[maxn];
int r[maxn];  // 與根節點的關係,如果值爲1則爲異性如果爲0則爲同性

int find(int x) {
    int t = pre[x];
    if (pre[x] != x) {
        pre[x] = find(pre[x]);
        r[x] = (r[x] + r[t]) % 2;  // 更新關係
    }
    return pre[x];
}

int main() {
   // freopen("input.txt", "r", stdin);
    int flag;
    int kase = 0;
    int T;
    int x, y;
    int n, m;
    scanf("%d", &T);
    while(T--) {
         flag = 1;
         scanf("%d%d", &n, &m);
         for(int i = 1; i <= n; i++) {
             pre[i] = i;
             r[i] = 0;
         }
         while(m--) {
             scanf("%d%d", &x, &y);
             if (!flag) continue;
             int rx = find(x);
             int ry = find(y);
             if (rx == ry) {
                if ((r[x] - r[y]) % 2 == 0) {
                    flag = 0;
                }
             }
             else {
                 pre[rx] = ry;
                 r[rx] = (r[x] - r[y] + 1) % 2;
             }
         }
         printf("Scenario #%d:\n",++kase);
         if(flag)
                printf("No suspicious bugs found!\n\n");
         else
                printf("Suspicious bugs found!\n\n");
    }

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

我們再把上一道題升級一下,從兩個種類拓展爲三個種類,由於三個種類的關係依舊是一個所以依然可以套帶權並查集模版。有幾個種類就取幾模,這裏是%3

食物鏈 POJ - 1182

這裏給出三種生物的關係,吃與同類的關係。由於這三種生物的關係依舊可以形成一個環,A吃B,B吃C,C又吃A。所以可以套種類並查集模版。
代碼
這裏寫圖片描述

#include<stdio.h>

const int maxn = 100000 + 10;

int pre[maxn], r[maxn];    // 父節點,與父節點的關係。0代表同類,1代表吃父節點,2代表被父節點吃。

int Find(int x)
{
    int t = pre[x];
    if(pre[x] != x)
    {
        pre[x] = Find(pre[x]);          // 壓縮路徑
        r[x] = (r[t] + r[x]) % 3;       // 更新關係
    }
    return pre[x];
}
int main()
{
    freopen("input.txt", "r", stdin);
    int i, N, T, ans;
    scanf("%d%d", &N, &T);
    for(i=0; i<=N; i++) {
        pre[i] = i;
        r[i] = 0;
    }
    ans = 0;
    while(T--)
    {
        int x, y, d;
        scanf("%d%d%d", &d, &x, &y);

        int rx = Find(x);
        int ry = Find(y);
        if(x>N || y>N || (d==2 && x==y) )
            ans++;
        else if(rx == ry && (r[x] - r[y] + 3)%3 != d - 1)
            ans++;
        else if(rx != ry) {
            pre[rx] = ry;
            r[rx] = ((d-1) + r[y] - r[x] + 3) % 3;
        }
    }
    printf("%d\n", ans);
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

ps

這篇文章有什麼錯誤的地方歡迎指正……

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章