并查集

并查集

问题:

1.并 合并 在合并时a,b的根谁指向谁

查 查根 找根的时候路径压缩,但仅能压缩该点至根这条路径上的点,对于其他叶子节点无能为力

  查询 查询时先查根,顺便路径压缩。//仍然不能保证所有的节点都为二级节点。//还是一开始就进行路径压缩,所以应该可以保证最多存在二级节点?

解决:

1.合并时 将成员少的树指向成员多的树。

(定义一棵树的秩为所有节点乘各自的高之和。秩从某种程度上可以表示运行该并查集的时间长短。秩越大,离根节点远的节点越多,即使有路径压缩,路径压缩的时候需要压缩的节点为该点至根这条路径上的点,也越多。

  原因:设a树1000节点,b树2节点,已为理想状态(秩H最小 = 2 + 999 * 2 + 1 * 2),除根节点均为二级节点。若a指向b,则此时的秩H为999 * 3 + 2 * 2 + 1.若b指向a,秩H = 1 * 3 + 999 * 2 + 1 * 2 + 1.

  实现:将根节点的f[x]置为该树成员个数的负值,可以省下rank数组的空间。

注意:如poj1182,将3 * n个点放在同一数组f[],则不能这样用,当f[1..n],f[n + 1..2 * n],f[2 * n + 1..3 * n]中同时有根节点,f[x] = -rank,在判断same(x,y)的时候会出错。若分开3个数组应该就可以。

结论:

查的时候 需要路径压缩,并的时候 将成员少的树指向成员多的树。

2.f[i] = i 和f[i] = -1的区别

f[i] = -1 可以用rank,即将根节点的f[x]置为该树成员个数的负值,以此节省时间。

但对于没有朋友的时候,状态不易区分,即有m个点均指向-1

f[i] = i更能表现其为单个节点时的分离的树的状态。


uva10608


#include <iostream>

#include <cstdio>

#include <cstring>

using namespace std;

const int maxn = 30005;

int f[maxn];

int find(int x)

{

    if (f[x] < 0) {//

        return x;

    }

    return f[x] = find(f[x]);

}

void merge(int x, int y)

{

    x = find(x);

    y = find(y);

    

    if (x == y && x > 0) { //若向用根节点的f[]记录该树的成员数,应注意该处

        return;

    }

    if(f[x] < f[y])// -3 < -1

    {

        f[x] += f[y]; //先加再赋值

        f[y] = x;

    }

    else{

        f[y] += f[x];

        f[x] = y;

    }

}


int main()

{

    int ce;

    cin >> ce;

    int n,m;

    

    while (ce --) {

        scanf("%d%d",&n,&m);

        memset(f, -1, sizeof(f));

        int x,y;

        for (int i = 0; i < m; i ++) {

            scanf("%d%d",&x,&y);

            merge(x,y);

        }

        int sum = 0;

        for (int i = 1; i <= n; i ++) {

           sum = min(sum,f[i]);

        }

        printf("%d\n",-sum);

    }

    return 0;

}




#include <iostream>

#include <stdio.h>

#include <string>

using namespacestd;

const int nmax =100000 +10;


int f[nmax];


int find_par(int a)

{

    if (f[a] == a) {

        return a;

        }

    else {

        f[a] =find_par(f[a]);//

        returnf[a];

    }

    

}

void merge(int a,int b)

{

    int ra =find_par(a);

    int rb =find_par(b);

    if (ra == rb) {

        return;

    }

   else

       f[rb] = ra;//

  

}

int main()

{

    long long n,m;

    int ca =1;

    while(cin >> n >> m){

        if (n ==0 && m ==0) {

            return0;

        }

        for (int i =1; i <= n; i ++) {

        f[i] = i;

    }

        int a,b;

        for (int i =0; i < m; i ++) {

            scanf("%d%d",&a,&b);

            merge(a,b);

        }

        int sum =0;

        for (int i =1; i <= n; i ++) {

            if (f[i] == i) {

                sum ++;

            }

        }

        cout <<"Case " << ca ++ <<": " <<sum <<endl;

        

        }

    return0;

}

poj1182 食物链

动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。 
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。 
有人用两种说法对这N个动物所构成的食物链关系进行描述: 
第一种说法是"1 X Y",表示X和Y是同类。 
第二种说法是"2 X Y",表示X吃Y。 
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。 
1) 当前的话与前面的某些真的话冲突,就是假话; 
2) 当前的话中X或Y比N大,就是假话; 
3) 当前的话表示X吃X,就是假话。 
你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。 

f[1..i..n],f[n + 1..i..2 * n],f[2 * n + 1..i..3 * n]分别表示i属于A,B,C类3种情况。同一个并查集中的情况同时为真或为假。

#include <iostream>

#include <cstdio>

using namespacestd;

const int maxn =150005;// * 3

int f[maxn],rk[maxn] = {0};

void init(int n)

{

    for (int i =1; i <=3 * n; i ++) {

        f[i] = i;//

    }

}

int find(int x)

{

    if (f[x] == x) {

        returnf[x];

    }

    else return f[x] =find(f[x]);

}

bool query(int x,int y)

{

    returnfind(x) ==find(y);

}

void unite(int x,int y)

{

    x = find(x);

    y = find(y);

    if (x == y) {

        return;

    }

    if (rk[x] <rk[y]) {

        f[x] = y;

    }

    else f[y] = x;

    if (rk[x] ==rk[y]) {

        rk[x] ++;

    }

}

int main()

{

    int n,k;

    cin >> n >> k;

    int d,x,y;

    int cnt =0;

    init(n);

    for (int i =0; i < k; i ++) {

        scanf("%d %d %d",&d,&x,&y);

        if (x <1 || y <1 ||x > n || y > n) {

            cnt ++;

        }

        else{

            if (d ==1) {

                if (query(x,n + y) ||query(x,y +2 * n) ) {

                    cnt ++;

                }

                else {

                    unite(x,y);

                    unite(x + n,y + n) ;

                    unite(x +2 * n,y +2 * n);

                }

            }

            else{

                if (query(x,y) ||query(x ,y +2 * n) ) {

                    cnt ++;

                    

                }

                else {

                    unite(x,y + n);

                    unite(x + n,y +2 * n);

                    unite(x +2 * n,y);

                }

            }

        }

    }

    cout << cnt <<endl;

    return0;

}

uva1160
用并查集在建图的时候判断是否会产生环。
<1,2> <2,3> <1,3> 将1,2当作点,在同一棵树里则表示有路联通,每次加入时,判断x,y是否在同一集合,若在,加入后则形成回路。


#include <iostream>

#include <cstdio>

#include <cstring>

using namespacestd;

const int maxn =100005;

int f[maxn];

int rk[maxn];

void init()

{

    for (int i =1; i < maxn; i ++) {

        f[i] = i;

    }

    memset(rk,0, sizeof(rk));

}

int find(int x)

{

    if (f[x] == x) {

        return x;

    }

    returnf[x] = find(f[x]);

}

void merge(int x,int y)

{

    x = find(x);

    y = find(y);

    if (x == y) {

        return;

    }

    if (rk[x] <rk[y]) {

        f[x] = y;

    }

    else f[y] = x;

    if (rk[x] ==rk[y]) {

        rk[x] ++;

    }

}

bool query(int x,int y)

{

    returnfind(x) == find(y);

}

int main()

{

    int a,b;

    int cnt =0;

    init();

    while (scanf("%d",&a) !=EOF) {

        if (a == -1) {

            cout << cnt <<endl;

            cnt = 0;

            init();

            getchar();getchar();

        }

        else{

            scanf("%d",&b);

            if (query(a,b)) {

                 cnt ++;

            }

            else {

                merge(a,b);

            }

        }

    }

    return0;

}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值