ACM_基础并查集

来源:互联网 发布:小米手机照片导入mac 编辑:程序博客网 时间:2024/05/22 00:05

基础并查集: 用于高效的查找某两个元素是否属于同一个集合;

一.普通的查找方式:

  1. 初始化所有的元素的父亲都是它自己:
    for(int i = 1; i <= n; i++) pa[i] = i;

  2. find函数, 每次向上找它的父亲, 直到找到它的根(根的特点是pa[i] == i):
    int find(int x){
    return x == pa[x] ? x : find(pa[x]);
    }
    这里写图片描述

  3. union_set函数:连接2个集合,实现的方法是用find函数找到2个集合的根分别为fx, fy, pa[fx] = fy; 就连接好了2个集合:
    Void union_set(int x, int y){
    int fx = find(x);
    int fy = find(y);
    pa[fx] = fy;
    }
    这里写图片描述

然后每次只需要判断一下是否find(x) == find(y)如果是说明他们是同一个集合, 否则不同集合;

二.并查集的实现方式:

  • 和上面的方法大致相同, 唯一不同的是find函数; 每次find之后都把该集合的该支链的所有元素的父亲改成根节点;这样下次再find的时候只需要找一次就知道该元素属于哪个集合了;实现的方式也很相同,只需要把find函数稍微改一下:
    int find(int x){
    return x == pa[x] ? x : pa[x] = find(pa[x]);
    }
    这里写图片描述
    当然这里树的深度还比较小了还不感觉什么, 但假如树足够深100层, 1000层的时候呢? 并查集会省下大量的时间;

例子

题目链接:http://poj.org/problem?id=1611
题目大意: 中文就不解释了;
做法:把同一个集合的所有元素都放到同一个集合里, 当放完之后, 检查一下0号同学在哪个集合, 再判断一下剩下的同学是否和它在同一个集合里面;

////  Created by fkjs on 2015-09-17//  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;const int INF = 0x3f3f3f3f;const int maxm = 505;const int maxn = 30000 + 10;typedef long long int ll;int n, m;int pa[maxn];int find(int x){//并查集的基础->find函数, 它的特点就是pa[x] = find(pa[x]), 这一语句可以把该路径上的所有点的父亲都改成根节点;    return x == pa[x] ? x : pa[x] = find(pa[x]);}void connect(int x, int y){//链接两个并查集    int fa = find(x);//找到根节点, 当然找的过程中会更新路上的点;    int fb = find(y);//同上;    pa[fa] = fb;//链接两个集合;}int main(void) {#ifdef LOCAL    //freopen("C:\\Users\\Administrator\\Desktop\\in.txt", "r", stdin);    //freopen("C:\\Users\\Administrator\\Desktop\\out.txt", "w", stdout);#endif    //ios_base::sync_with_stdio(0);    while(scanf("%d%d", &n, &m) == 2 && (n || m)){        for(int i = 0; i < n; i++) pa[i] = i;        for(int i = 0; i < m; i++){            int len; scanf("%d", &len);            int x; scanf("%d", &x);            int tp = x;            for(int i = 1; i < len; i++){                scanf("%d", &x);                connect(x, tp);                tp = x;            }        }        int ans = 0;        int p = find(0);//找到最初感染者所在的集合, 它的根是p;        for(int i = 0; i < n; i++)//凡是根是p的人都被感染了;            if(find(i) == p)                ans++;        printf("%d\n", ans);    }    return 0;}
0 0