并查集專題整理

kuangbin專題

模板

int find(int x) {
    return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void join(int x, int y) {
    int fx = find(x), fy = find(y);
    if (fx != fy) fa[fx] = fy;
}

關(guān)于并查集的一點(diǎn)心得

大家都說帶權(quán)并查集的起點(diǎn)是食物鏈( POJ - 1182 ),但是可能我比較笨,沒有從這道題里領(lǐng)會(huì)到很深的東西,一直到做到蟲子的一生( POJ - 2492 )才有點(diǎn)領(lǐng)悟到,也多虧了在做Navigation Nightmare( POJ - 1984 )時(shí)進(jìn)巨的點(diǎn)撥。

并查集

并查集就是一棵樹,與之相關(guān)的最小生成樹的Kruskal,從一個(gè)環(huán)圖中找一棵由最短的邊組成的樹,只要一棵,也就是說每個(gè)點(diǎn)都必須至少有一條路可達(dá),這就是最小生成樹。
把關(guān)聯(lián)的結(jié)點(diǎn)用父子關(guān)系鏈接,這樣,有關(guān)的結(jié)點(diǎn)的根節(jié)點(diǎn)就會(huì)是同一個(gè),可以判斷出是否在一棵樹上。同時(shí)可以判環(huán),如果兩個(gè)點(diǎn)已知相連,并且有同一個(gè)根節(jié)點(diǎn),那就是有環(huán)。
并查集的兩個(gè)常用操作,一個(gè)是初始化,所有的點(diǎn)的父節(jié)點(diǎn)都是自己,一個(gè)是查找當(dāng)前結(jié)點(diǎn)的根節(jié)點(diǎn),一個(gè)是合并兩棵樹。查找根節(jié)點(diǎn)一般用遞歸,找到最上方的結(jié)點(diǎn),其父節(jié)點(diǎn)就是自身。為了提高時(shí)間效率,查找時(shí)加上壓縮路徑,即每個(gè)點(diǎn)的父節(jié)點(diǎn)都直接改變成根節(jié)點(diǎn)。合并樹操作在帶權(quán)并查集中發(fā)揮很大作用。

并查集詳解這篇文章講得淺顯易懂,很不錯(cuò)。

帶權(quán)并查集

而帶權(quán)并查集,其中的權(quán)需要轉(zhuǎn)化成子節(jié)點(diǎn)與父節(jié)點(diǎn)之間的聯(lián)系。這樣向上查找時(shí)就能發(fā)現(xiàn)父節(jié)點(diǎn)和子節(jié)點(diǎn)之間的關(guān)系,以此來進(jìn)行計(jì)算。
帶權(quán)并查集的壓縮路徑方法是,在遞歸向上查找的同時(shí),因?yàn)檫f歸是直接到達(dá)最深處然后向上回溯的,所以只需要對每個(gè)點(diǎn)都做一次累加,這樣回到原來的位置時(shí)就是全部的累加。合并樹操作各有不同,主要是創(chuàng)建父節(jié)點(diǎn)的操作。
舉個(gè)例子,蟲子的一生( POJ - 2492 )中用權(quán)數(shù)組表示兩個(gè)蟲子的性別關(guān)系,更新時(shí)就只要考慮一下同性還是異性即可。

G - Supermarket

POJ - 1456

題解

貪心。
給出一堆商品的收益和必須要賣出的deadline,求能獲得的最大收益。
WA了好幾次,本來以為自己不會(huì)出這種小bug了,看起來還是編碼能力有待提高。也不是很懂為什么一道簡單貪心放在并查集上。
先把所有商品的兩個(gè)屬性封裝,然后排序,從頭開始找最大的,放在能放的最后面的位置。

代碼

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 10005;

struct pord{
    int p, d;
}mp[maxn];
int n, vis[maxn];

bool cmp(pord a, pord b){
    return a.p > b.p;
}

int main(){
    while (~scanf("%d", &n)){
        for (int i = 1; i <= n; ++i)
            scanf("%d%d", &mp[i].p, &mp[i].d);
        sort(mp+1, mp+n+1, cmp);
        memset(vis, 0, sizeof(vis));
        int ans = 0;
        for (int i = 1; i <= n; ++i)
            for (int j = mp[i].d; j > 0; --j)
                if (!vis[j]){
                    vis[j] = 1;
                    ans += mp[i].p;
                    break;
                }
        printf("%d\n", ans);
    }
    return 0;
}

H - Parity game

POJ - 1733

題解

并查集+離散化。
有一個(gè)01串,1e9位。給出一些子串,和他們中有奇數(shù)個(gè)還是偶數(shù)個(gè)1,求這些中有幾個(gè)是對的。
因?yàn)殚L度太長所以開不下這么多數(shù)組,而子串只有五千個(gè),所以需要哈希一下。只要存最多一萬個(gè)數(shù)字就可以。為了避免不在一起的相鄰所以多存一位,就是兩萬位。然后用一個(gè)數(shù)組存當(dāng)前下標(biāo)之前有奇數(shù)還是偶數(shù)個(gè)1,最后用并查集求解。

代碼

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 100005;

int fa[maxn], val[maxn], n, m;
int hashSet[maxn];

struct {
    int u, v, w;
}node[maxn];

int find(int n){
    int k = fa[n];
    if(fa[n] != n){
        fa[n] = find(fa[n]);
        val[n] = (val[n] + val[k])%2;
    }
    return fa[n];
}

void init(){
    for (int i = 0; i <= n; ++i)
        val[i] = 0, fa[i] = i;
}

int main(){
    while (~scanf("%d", &n)){
        int i, k = 0;
        
        //init放在這里會(huì)RE
        
        scanf("%d", &m);
        for (i = 0; i < m; ++i){
            char s[5];
            scanf("%d%d%s", &node[i].u, &node[i].v, s);
            node[i].w = s[0] == 'e'? 0:1;
            
            hashSet[k++] = node[i].u - 1;
            hashSet[k++] = node[i].u;
            hashSet[k++] = node[i].v - 1;
            hashSet[k++] = node[i].v;
        }
        hashSet[k++] = n;
        hashSet[k++] = n - 1;
        
        sort(hashSet, hashSet+k);
        n = (int)(unique(hashSet, hashSet+k) - hashSet);
        
        init();
        //init放這里就AC
        
        for (i = 0; i < m; ++i){
            int u = (int)(lower_bound(hashSet, hashSet+n, node[i].u-1) - hashSet);
            int v = (int)(lower_bound(hashSet, hashSet+n, node[i].v) - hashSet);
            
            int fu = find(u), fv = find(v);
            
            if (fu == fv && (val[u] + node[i].w)%2 != val[v])
                break;
            if (fu < fv){
                fa[fv] = fu;
                val[fv] = (val[u] + node[i].w - val[v] + 2) % 2;
            }
            if (fu > fv){
                fa[fu] = fv;
                val[fu] = (val[v] - node[i].w - val[u] + 2) % 2;
            }
        }
        printf("%d\n", i);
    }
    return 0;
}

I - Navigation Nightmare

POJ - 1984

題解

帶權(quán)并查集。
題目給出一些路,路有起點(diǎn)、終點(diǎn)、長度、方向四個(gè)屬性。要求回答提問的兩個(gè)點(diǎn)之間的曼哈頓距離(x軸距離+y軸距離),注意提問給出了提問的時(shí)間,此時(shí)的地圖不一定完整(不是所有的路都能用)。
卡了很久。都是TLE,因?yàn)橐粋€(gè)地方?jīng)]有把二層循環(huán)改為O(nlogn)的寫法。
col數(shù)組存儲(chǔ)當(dāng)前點(diǎn)相對于父節(jié)點(diǎn)的偏移量,ed數(shù)組存儲(chǔ)邊的四個(gè)屬性,q數(shù)組存儲(chǔ)問題。之所以用數(shù)組是為了避免因?yàn)閱栴}的順序是亂的,用cmp函數(shù)排個(gè)序。
find查找函數(shù)中有路徑壓縮,把樹上的所有點(diǎn)的偏移量直接改為到根節(jié)點(diǎn)的偏移量。
join合并函數(shù)根據(jù)路的方向和長度來更新偏移量。
最后搜索,如果不在同一棵樹上就不成立,否則維護(hù)偏移量。

代碼

#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 40005;

int fa[maxn], ans[maxn];
int n, m;

struct{
    int x, y;
}col[maxn];

struct {
    int x, y, d;
    char s[2];
}ed[maxn];

struct que{
    int a, b, idx, n;
}q[maxn];

bool cmp(que a, que b) {
    return a.idx < b.idx;
}

int find(int n) {
    if (n != fa[n]) {
        int k = fa[n];
        fa[n] = find(fa[n]);
        col[n].x += col[k].x;
        col[n].y += col[k].y;
    }
    return fa[n];
}

void join(int u, int v, int i) {
    int fu = find(u);
    int fv = find(v);
    if (fu == fv) return;
    fa[fv] = fu;

    int rx = col[v].x;
    int ry = col[v].y;
    switch (ed[i].s[0]){
        case 'E': 
            col[fv].x = col[u].x + ed[i].d - col[v].x;
            col[fv].y = col[u].y - col[v].y;
            break;
        case 'W': 
            col[fv].x = col[u].x - ed[i].d - col[v].x;
            col[fv].y = col[u].y - col[v].y;
            break;
        case 'S':
            col[fv].x = col[u].x - col[v].x;
            col[fv].y = col[u].y - ed[i].d - col[v].y;
            break;
        case 'N':
            col[fv].x = col[u].x - col[v].x;
            col[fv].y = col[u].y + ed[i].d - col[v].y;
            break;
    }
}

int main() {
    while (~scanf("%d%d", &n, &m)) {
        for (int i = 0; i <= n; ++i) fa[i] = i;

        for (int i = 1; i <= m; ++i)
            scanf("%d%d%d%s", &ed[i].x, &ed[i].y, &ed[i].d, ed[i].s);

        int k;
        scanf("%d", &k);
        for (int i = 1; i <= k; ++i) {
            scanf("%d%d%d", &q[i].a, &q[i].b, &q[i].idx); // input questions
            q[i].n = i;
        }

        sort(q + 1, q + k + 1, cmp);
        for (int i = 1; i <= k; ++i) {
            int cur = i == 1 ? 1 : q[i - 1].idx + 1;
            for (int j = cur; j <= q[i].idx; ++j) {
                join(ed[j].x, ed[j].y, j);
            }

            if (find(q[i].a) != find(q[i].b))
                ans[q[i].n] = -1;
            else
                ans[q[i].n] = abs(col[q[i].a].x - col[q[i].b].x) + abs(col[q[i].a].y - col[q[i].b].y);
        }

        for (int i = 1; i <= k; ++i)
            printf("%d\n", ans[i]);
    }
    return 0;
}

J - A Bug's Life

POJ - 2492

題解

帶權(quán)并查集。
給出n條蟲子的m條交配記錄。假設(shè)蟲子有兩個(gè)性別,都是異性戀。根據(jù)記錄判斷假設(shè)是否正確。
簡單的帶權(quán)并查集應(yīng)用。難點(diǎn)主要在于如何選擇權(quán)數(shù)組的形式。
這里用權(quán)數(shù)組re來表示當(dāng)前蟲子和上一級(jí)蟲子的關(guān)系,0為同性,1為異性。要注意權(quán)數(shù)組意義,以及如何在合并樹操作時(shí)處理權(quán)數(shù)組。

代碼

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 2005;

int fa[maxn], re[maxn];
int n, m;
bool flag;

void init(){
    for (int i = 0; i <= n; ++i)
        fa[i] = i;
    memset(re, 0, sizeof(re));
    flag = true;
}

int find(int x){
    if (x == fa[x])
        return x;
    int t = find(fa[x]);
    re[x] = (re[x] + re[fa[x]]) % 2;
    fa[x] = t;
    return fa[x];
}

bool join(int x, int y){
    int fx = find(x), fy = find(y);
    if (fx == fy){
        if (re[x] == re[y])
            return false;
        return true;
    }
    fa[fy] = fx;
    re[fy] = (re[y] - re[x] + 1) %2;
    return true;
}

bool solve(){
    int a, b;
    bool f = true;
    scanf("%d%d", &n, &m);
    init();
    for (int i = 1; i <= m; ++i){
        scanf("%d%d", &a, &b);
        if (!join(a, b))
            f = false;
    }
    return f;
}

int main(){
    int t;
    scanf("%d", &t);
    for (int i = 1; i <= t; ++i){
        if (solve())
            printf("Scenario #%d:\nNo suspicious bugs found!\n\n", i);
        else
            printf("Scenario #%d:\nSuspicious bugs found!\n\n", i);
    }
    return 0;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容