并查集

POJ 1182 食物链

添加一个维护当前节点与父节点关系的信息,每次压缩时更新关系,另外join的时候也要根据当前节点和父节点关系以及两个父节点关系更新;

单case,不用EOF处理。

# include <cstdio>
const int maxn = 50005;
int n, k;
int p[maxn];
int g[maxn];
int find(int x) {
    if (x == p[x]) return x;
    int t = find(p[x]);
    g[x] = ((g[p[x]] + g[x]) % 3 + 3) % 3;
    p[x] = t;
    return t;
}
# define testin freopen("in.txt", "r", stdin)
int main()
{
    //while (scanf("%d%d", &n, &k) != EOF) {
        scanf("%d%d", &n, &k);
        for (int i = 1; i <= n; ++i) p[i] = i, g[i] = 0;
        int ans = 0;
        for (int i = 0, od, x, y; i < k; ++i) {
            scanf("%d%d%d", &od, &x, &y);
            if (x>n||y>n || (od==2 && x==y)) {
                ++ans;
                continue;
            } else {
                int fx = find(x);
                int fy = find(y);
                if (fx != fy) {
                    p[fx] = fy;
                    g[fx] = ((g[y]-g[x]+od-1)%3+3)%3;
                } else if (((g[x]-g[y])%3+3)%3 != od-1){
                    ++ans;
                }
            }
        }
        printf("%d
", ans);
    //}

    return 0;
}

HDU 2473

带删除节点的并查集

对每个节点,映射射到一个虚拟节点,然后对虚拟节点维护并查集,删除节点时,将对应节点应射到一个新的节点即可,由于查询次数有限,浪费的空间可以接受。

# include <cstdio>
const int maxn = 100005;
const int maxm = 1000005;
int n, m;
int fa[maxn + maxm];
bool isRoot[maxn + maxm];
int link[maxn];
int id;
int find(int x) {
    if (x != fa[x]) return fa[x]=find(fa[x]);
}
void join(int x, int y)
{
    x = find(x);
    y = find(y);
    if (x != y) fa[x] = y;
}
void del(int x)
{
    link[x] = id++;
}
# define testin freopen("in.txt", "r", stdin)
int main()
{
    int ica = 0;
    while (scanf("%d%d", &n, &m), n||m) {
        for (int i = 0; i < n+m; ++i) isRoot[i]=false, fa[i]=i;
        for (int i = 0; i < n; ++i) link[i] = i;
        char od[5];
        id = n;
        for (int i = 0, x, y; i < m; ++i) {
            scanf("%s%d", od, &x);
            if (od[0] == 'M') {
                scanf("%d", &y);
                join(link[x], link[y]);
            } else {
                del(x);
            }
        }
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            if (!isRoot[ find(link[i]) ]) {
                ++ans;
                isRoot[ find(link[i]) ] = true;
            }
        }
        printf("Case #%d: %d
", ++ica, ans);
    }
    return 0;
}

ZOJ 3261

带删除边的并查集。

WA:维护的并不是树,每个节点和多个节点直接有关系,不能直接删除并查集中的边。

WA 了很多次,原因找到了:再离线保存query处理destroy的情况时,首先保存了query[i][1] = x, 然后条件交换了x,y,然后query[i][2]=y。(x>y时,导致query[i][2]==x)

# include <map>
# include <utility>
# include <algorithm>
# include <cstdio>
# include <cstring>

const int maxn = 10005;
const int maxm = 20005;
const int maxq = 50005;

int n, m, q;
int power[maxn];
int u[maxm], v[maxm];
bool des[maxm];
int query[maxq][3];
int p[maxn];

std::map< std::pair<int, int>, int >mp;

int find(int x) {
    return x==p[x] ? x:p[x]=find(p[x]);
}
void join(int x, int y) {
    x = find(x), y = find(y);
    if (x == y) return ;
    if (x > y) std::swap(x, y);
    if (power[x] < power[y]) p[x] = y;
    else p[y] = x;
}
# define testin freopen("in.txt", "r", stdin)
int main()
{
    bool first = true;

    while (scanf("%d", &n) != EOF) {
        mp.clear();
        for (int i = 0; i < n; ++i) scanf("%d", &power[i]);
        scanf("%d", &m);
        for (int i = 0; i < m; ++i) {
            scanf("%d%d", &u[i], &v[i]);
            if (u[i] > v[i]) std::swap(u[i], v[i]);
            mp[std::make_pair(u[i], v[i])] = i+1;
        }
        memset(des, false, sizeof(des[0])*m);
        scanf("%d", &q);
        char od[15];
        for (int i = 0, x, y; i < q; ++i) {
            scanf("%s%d", od, &x);
            if (od[0] == 'q') {
                query[i][0] = 0;
                query[i][1] = x;
            } else {
                scanf("%d", &y);
                if (x > y) std::swap(x, y);
                int t = mp[ std::make_pair(x,y) ] - 1;
                if (t >= 0) des[t] = true;
                query[i][0] = 1;
                query[i][1] = x;
                query[i][2] = y;
            }
        }
        for (int i = 0; i < n; ++i) p[i] = i;
        for (int i = 0; i < m; ++i) {
            if (!des[i]) {
                join(u[i], v[i]);
            }
        }
        for (int i = q-1; i >= 0; --i) {
            if (!query[i][0]) {
                int t = find( query[i][1] );
                if (power[t] <= power[ query[i][1] ]) t = -1;
                query[i][2] = t;
            } else {
                join( query[i][1], query[i][2] );
            }
        }
        if (first) first = false;
        else printf("
");
        for (int i = 0; i < q; ++i) {
            if (!query[i][0]) printf("%d
", query[i][2]);
        }
    }

    return 0;
}

cf 356A Knight Tournament

感觉这是一道并查集的好题,记得当时想了很久没思路,赛后看别人的代码也没弄明白。

其实这道题完全体现了并查集的用途:维护集合;不同点在于需要如何维护,即并的方式。

当x打败y时,只要将ans[y]更新为x即可,所以并查集维护的并不是淘汰关系。

将没有淘汰的选手作为根节点,两个未淘汰选手x,y(x<y)之间的淘汰了的选手加入到y的集合中,即当前节点x的根节点是右侧第一个未被淘汰的节点。

# include <stdio.h>
using namespace std;

const int maxn = 300005;

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

int find(int x) {
    return x==p[x] ? x:p[x]=find(p[x]);
}
void join(int l, int r, int x)
{
    while ((l=find(l)) <= r) {
        if(l != x) ans[l] = x, p[l] = l+1;
        ++l;
    }
}
int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 1; i <= n+1; ++i) p[i] = ans[i] = i;
    for (int i = 1, x, l, r; i <= m; ++i) {
        scanf("%d%d%d", &l, &r, &x);
        join(l, r, x);
    }
    for (int i = 1; i <= n; ++i) {
        if (i > 1) putchar(' ');
        printf("%d", (ans[i]==i ? 0:ans[i]));
    }
    putchar('
');

    return 0;
}
原文地址:https://www.cnblogs.com/txd0u/p/4147534.html