nishikino-curtis

并查集的基础概念及实现

部分内容引用自wikipedia.org

  • 并查集(Union-Find Sets)是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题,定义了两个用于此数据结构的操作:
  • Find:确定元素属于哪一个子集。它可以被用来确定两个元素是否属于同一子集。
  • Union:将两个子集合并成同一个集合。
  • 在实际应用中,常随着find操作进行路径压缩,均摊复杂度为反阿克曼函数,接近线性。

    Source

    int find(int x)//返回x的父节点,并将其挂载到根节点上
    {
    if(x==fa[x])return x;
    return x=find(fa[x]);
    }
    int link(int x,int y)
    {
    int fx=find(x),fy=find(y);
    if(fx!=fy)fa[fx]=fy;
    }

Example CodeForces277A LearningLanguages

Description

  • The "BerCorp" company has got n employees. These employees can use m approved official languages for the formal correspondence. The languages are numbered with integers from 1 to m. For each employee we have the list of languages, which he knows. This list could be empty, i. e. an employee may know no official languages. But the employees are willing to learn any number of official languages, as long as the company pays their lessons. A study course in one language for one employee costs 1 berdollar.
  • Find the minimum sum of money the company needs to spend so as any employee could correspond to any other one (their correspondence can be indirect, i. e. other employees can help out translating).
  • BerCorp公司有n名雇员。这些雇员共掌握m种官方语言(以从1到m的整数编号)用于正式交流。对于每个雇员,我们有一个他掌握的语言列表,列表可以为空,这意味着一个雇员可能不掌握任何官方语言。但是雇员们愿意学习语言,只要公司为课程付费。每名雇员学习一种语言需要花费 1 Ber元。请找出能让所有雇员直接或间接(可由其他雇员提供中间翻译)交流的最小花费。

Input&Output

Input

  • The first line contains two integers n and m (2 ≤ n, m ≤ 100) — the number of employees and the number of languages.
  • Then n lines follow — each employee's language list. At the beginning of the i-th line is integer ki (0 ≤ ki ≤ m) — the number of languages the i-th employee knows. Next, the i-th line contains ki integers — aij (1 ≤ aij ≤ m) — the identifiers of languages the i-th employee knows. It is guaranteed that all the identifiers in one list are distinct. Note that an employee may know zero languages.
  • The numbers in the lines are separated by single spaces.
  • 第一行为两个整数n,m(2<=n,m<=100),为雇员的数量和语言的数量。
  • 接下来n行,每行首先有一个整数ki(0<=ki<=m),为雇员i掌握的语言数量,接下来有ki个整数,为雇员i掌握的语言。这意味着一个表中所有的编号都不同。注意一个雇员可能掌握0种语言。
  • 每行中的数字都用一个空格隔开。

Output

  • Print a single integer — the minimum amount of money to pay so that in the end every employee could write a letter to every other one (other employees can help out translating).
  • 一个整数——能让所有雇员直接或间接交流的最小花费。

Sample

Input#1

5 5
1 2
2 2 3
2 3 4
2 4 5
1 5

Output#1

0

Input#2

8 7
0
3 1 2 3
1 1
2 5 4
2 6 7
1 3
2 7 4
1 1

Output#2

2

我会告诉你第三组懒得粘了吗

Solution

  • 非常裸的并查集,同时需要维护联通块,处理结束后,输出联通块个数-1。容易证明:如果读入结束后仍有大于1个联通块,对于联通块A,B,一定存在至少一种语言,A中有人掌握,且B中无人掌握,此时令B中一人学习一种该语言即可。以此类推,直到联通块总数为1,则需要sum-1个人学习新语言。
    注意一些细节:
  • 可以开一个二维vector,维护掌握每种语言的员工编号,每当读入一名员工掌握一种语言,尝试将其与所有已知的掌握该语言的员工联通,完成后将其压入vector。
  • 如果所有员工都掌握零种语言,答案应为员工数,因为此时所有员工都要学习一门语言。
  • 代码如下:

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<cstring>
    #include<vector>
    #define maxn 105
    using namespace std;
    int n,m,k,l,spj,sum,fa[maxn];//sz[maxn];
    vector<vector<int> > v(maxn);
    int find(int x)
    {
    if(x==fa[x])return x;
    else return fa[x]=find(fa[x]);
    }
    void link(int x,int y)
    {
    int fx=find(x),fy=find(y);
    if(fx!=fy) fa[fx]=fy,sum--;
    }
    int main()
    {
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i)
    {
        fa[i]=i;
        scanf("%d",&k);
        if(!k){spj++;sum++;continue;}
        sum++;
        for(int j=1;j<=k;++j)
        {
            scanf("%d",&l);
            for(int p=0;p<v[l].size();++p)link(i,v[l][p]);
            v[l].push_back(i);
        }
    }
    if(spj==n){printf("%d",n);return 0;}
    printf("%d",sum-1);
    return 0;
    }

Example Luogu1955 NOI2015 程序自动分析

Description

  • 在实现程序自动分析的过程中,常常需要判定一些约束条件是否能被同时满足。
  • 考虑一个约束满足问题的简化版本:假设x1,x2,x3...代表程序中出现的变量,给定n个形如xi=xj或xi≠xj的变量相等/不等的约束条件,请判定是否可以分别为每一个变量赋予恰当的值,使得上述所有约束条件同时被满足。例如,一个问题中的约束条件为:x1=x2,x2=x3,x3=x4,x4≠x1,这些约束条件显然是不可能同时被满足的,因此这个问题应判定为不可被满足。
  • 现在给出一些约束满足问题,请分别对它们进行判定。

Input&Output

Input

  • 输入文件的第1行包含1个正整数t,表示需要判定的问题个数。注意这些问题之间是相互独立的。
  • 对于每个问题,包含若干行:
  • 第1行包含1个正整数n,表示该问题中需要被满足的约束条件个数。接下来n行,每行包括3个整数i,j,e,描述1个相等/不等的约束条件,相邻整数之间用单个空格隔开。若e=1,则该约束条件为xi=xj;若e=0,则该约束条件为xi≠xj;

Output

  • 输出文件包括t行。
  • 输出文件的第 k行输出一个字符串“ YES” 或者“ NO”(不包含引号,字母全部大写),“ YES” 表示输入中的第k个问题判定为可以被满足,“ NO” 表示不可被满足。

Sample

Input

2
2
1 2 1
1 2 0
2
1 2 1
2 1 1

Output

NO
YES

Solution

  • 我这种蒟蒻也就刷刷水题了,对于每个类型为1的条件,先尝试并,然后再处理类型为0的条件,如果冲突(在同一颗树中),直接输出NO,break掉。
  • 需要注意的是,有三个毒瘤测试点,所以要离散化。
  • 代码如下:
#include<iostream>
#include<cstdio>
#include<algorithm>
#define maxn 100010
using namespace std;
struct node {
    int x, y,type;
    node(int x=0, int y=0,int t=0) {
        x = x;
        y = y;
        type=t;
    }
    bool operator <(const node &a)const{
        return type>a.type;//操作一波可以把所有类型为1的条件前置
    }
}opt[maxn];
int n, a, b,t,m,fa[maxn<<1],big[200010];
int find(int x) {
    return(x == fa[x]) ? x : fa[x] = find(fa[x]);
}
void link(int x, int y) {
    int fx = find(x), fy = find(y);
    if (fx != fy)fa[fx] = fy;
}
bool same(int x, int y) {
    return find(x) == find(y);
}
int main()
{
    scanf("%d", &n);
    for (int i = 1;i <= n;++i) {
        for (int j = 1;j <= 200010;++j)fa[j] = j;
        int ptr2=0;bool flg=true;
        scanf("%d", &m);
        for (int j = 1;j <= m;++j) {
            scanf("%d%d%d", &a, &b, &t);
            opt[j].x = a, opt[j].y = b,opt[j].type=t;
            big[++ptr2]=a,big[++ptr2]=b;
        }
        sort(opt+1,opt+1+m);
        sort(big+1,big+1+ptr2);
        int tot=unique(big+1,big+1+ptr2)-(big+1);
        for(int j=1;j<=m;++j){
            opt[j].x=lower_bound(big+1,big+1+tot,opt[j].x)-big,
            opt[j].y=lower_bound(big+1,big+1+tot,opt[j].y)-big;
            if(opt[j].type)link(opt[j].x,opt[j].y);
            else if(!opt[j].type&&same(opt[j].x,opt[j].y)){puts("NO");flg=false;break;}
        }
        if(flg)puts("YES");
    }
    return 0;
}

相关文章:

  • 2021-10-07
  • 2021-11-30
  • 2021-08-12
  • 2022-02-06
  • 2021-07-08
  • 2021-09-20
  • 2021-09-27
  • 2021-10-02
猜你喜欢
  • 2022-01-08
  • 2021-06-08
  • 2019-06-01
  • 2021-06-27
相关资源
相似解决方案