ACM_种类并查集

来源:互联网 发布:java 什么是异常 编辑:程序博客网 时间:2024/05/21 06:46

简介

种类并查集:
和基础并查集有很大一部分相同, 多了一个判断2个元素是否属于同一个集团(不是集合, 集合是用来判断2个元素是否能够判断他们属不属于同一个集团:有点绕, 举个例子, 假如知道1和2在不同的集团, 3和4在不同集团,我们就不能判断1和3是否属于一个集团,而集团是用来判断他们是否在同一个集团假如:已知1和2在不同集团,2和3在不同集团, 那么我们就知道1和3在同一个集团);

要懂种类并查集就必须知道基础并查集:http://blog.csdn.net/fkjslee/article/details/48809903

特色

种类并查集多了一个集团, 我习惯用group[i]来代表i属于哪个集团;注意:group[i]代表的是他和pa[i]的关系, 并不是最后加入所有元素后i属于哪个集团;
举个例子:我们用0代表group[i]和group[pa[i] 属于同一个集团, 1代表不同;
现在有pa[3] = 4; group[3] = 1; 这里的group[3]仅仅代表3和4属于属于不同的集团!!!!!(非常重要, 不理解这点你会不能理解种类并查集的!!!), 至于最后3 在哪个集团会在find函数中更新, 下面我会用图示介绍;
同样分成种类并查集也分成两个:find 和union_set;

因为不同的种类并查集有一定差异:现在用一个例题来说明:POJ1182 http://poj.org/problem?id=1182

做法:每次找x和y是不是在同一个集合中:
如果是, 判断一下x和y满不满足它所给的关系。
如果不是, 说明这句话肯定为真:因为另一个集合中的所有元素可以一起变使得它满足x和y的关系; 同时合并两个集合;

当然这么说很简单, 具体做法及解释在下面:

最开始初始化pa[i] = i; group[i] = 0;

  1. find函数:
    不同的种类并查集有不同的find函数, 不过一般是这样:
    下面代码中:
    x 指的是需要查找的数;

    group[x]指的是x和他的父节点(pa[x])的关系:本题中
    如果group[x] = 0代表x和pa[x]属于同一个物种,
    如果group[x] = 1代表x吃pa[x],
    如果group[x] = 2代表x被pa[x]吃;(当然不同的题这里可能不同)

    fa代表的是这个集合的根节点;

    种类并查集的精华1:
    每次find的时候更新它属于哪个group, 因为他之前的父亲的父亲已经是根节点了, 所以可以利用它之前的父亲和它和之前父亲的关系更新它和根节点的关系,当然更新后的group还是和父亲的关系; 不过父亲已经变成了根节点了;

    find的代码:

    int find(int x) {
    if(pa[x] == x) return x;
    int fa = find(pa[x]);
    group[x] = (group[x] + group[pa[x]]) % 3;
    return pa[x] = fa;
    }

  2. unios_set函数:
    当然这个也要看题, 不过大同小异;
    代码中的变量解释:
    fx: x的根节点, fy: y的根节点;

    group[x] : x和它父亲的关系(见上)

    type:指传入的x和y是什么关系, 1代表他们是同类, 2代表x吃y(和题目中是一样的);

pa[x]代表x的父亲;

代码解释:

if(fx == fy) 表示他们属于同一个集合, 因为x和y已经find过了, 所以他们的group都是和根节点的关系(当然他们的根节点是一样的因为fx = fy)这个时候只需要看满不满足type就OK了;

下面是种类并查集的精华2了:

如果他们不是同一个集合, 就链接两个集合;

如果你不是很熟悉, 可以把所有的情况的考虑一下;
这里提一下, group[fx] = grou[fy] = 0; 因为他们没有父亲, 而我们初始化的时候都初始化成0的;
比如y和x是同一个group 1.type == 1 那么fx和fy同一类; group[fx] 不变 为 0;
2.type == 2 因为我们会更新pa[fx] = fy; type == 2 说明x 吃y 本来x 和y 的group是一样的, 所以只要把x的group+1就OK了, 因为x的group加了1, 所以该集合中的所有元素的group必定也+1(他们是一体的) 所以不必更新group[x] 只需要更新group[fx] += 1(因为它的group = 0, 所以直接等于1页可以)就OK了, 这样就实现了该集合的所有元素的group都更新了(因为在find函数的时候会更新group)
你可以再考虑一下group[y] - grou[x] = 1 后者2 或者-1 或者-2;就会发现他们可以用一个式子满足group[fx] = (group[y] - group[x] + 3 + (type - 1)) % 3;

union_set函数的代码:
bool union_set(int type, int x, int y) {
int fx = find(x);
int fy = find(y);
if(fx == fy) {
if(type == 1 && group[x] != group[y]) return true;
if(type == 2 && (group[y] + 1) % 3 != group[x]) return true;
return false;
}
group[fx] = (group[y] - group[x] + 3 + (type - 1)) % 3;
pa[fx] = fy;
return false;
}

总的代码见下:

////  Created by fkjs on 2015-09-19//  Copyright (c) 2015 fkjs. All rights reserved.////#pragma comment(linker, "/STACK:1024000000,1024000000")#include <algorithm>#include <cctype>#include <cmath>#include <cstdio>#include <cstdlib>#include <cstring>#include <iomanip>#include <iostream>#include <map>#include <queue>#include <string>#include <set>#include <sstream>#include <stack>#include <vector>#define clr(x) memset(x, 0, sizeof(x))using namespace std;typedef long long int ll;const int INF = 0x3f3f3f3f;const int maxn = 5e4 +10;int pa[maxn], group[maxn];int find(int x) {    if(pa[x] == x) return x;    int fa = find(pa[x]);    group[x] = (group[x] + group[pa[x]]) % 3;    return pa[x] = fa;}bool union_set(int type, int x, int y) {    int fx = find(x);    int fy = find(y);    if(fx == fy) {        if(type == 1 && group[x] != group[y]) return true;        if(type == 2 && (group[y] + 1) % 3 != group[x]) return true;        return false;    }    group[fx] = (group[y] - group[x] + 3 + (type - 1)) % 3;    pa[fx] = fy;    return false;}int main(void) {#ifdef LOCAL    freopen("C:\\Users\\Administrator\\Desktop\\in.txt", "r", stdin);    freopen("C:\\Users\\Administrator\\Desktop\\out1.txt", "w", stdout);#endif    //ios_base::sync_with_stdio(0);    int n, k;    scanf("%d%d", &n, &k) == 2;        for(int i = 1; i <= n; i++) pa[i] = i;        for(int i = 1; i <= n; i++) group[i] = 0;        int ans = 0;        for(int i = 1; i <= k; i++) {            int x, y, z;            scanf("%d%d%d", &x, &y, &z);            if(y > n || z > n || (x == 2 && y == z)){                ans++;                continue;            }            if(find(y) == find(z)) {                if(x == 1 && group[y] != group[z]) ans++;                if(x == 2 && (group[z]+1) % 3 != group[y]) ans++;            } else {                if(union_set(x, y, z)) ans++;            }        }        printf("%d\n", ans);    return 0;}
1 0