• 并查集


    例题:

    并查集:

    在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并

    其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大

    若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高

    根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。

    洛谷P3367

    传送门

    先上代码:

    关于并查集和路径压缩:

    有a,b,c三个人

    假设a和b打架了,a做了b的小弟。则令f[a]=b;

    后来a打赢了c 黑社会

    那么c就是a的小弟了。所以,令f[c]=a;

    但是,c不知道b,这不符合要求。

    所以,我们必须让c的大哥变成最大的老大。

    int find(int k){
        if(f[k]==k)return k;
        return find(f[k]);
    }
    
    f[c]=find(a);

    这时,我们可以使途中经过的人的大哥也变成老大

    //路径压缩
    int find(int k){
        if(f[k]==k)return k;
        return f[k]=find(f[k]);
    }
    
    f[c]=find(a);

    而判定两个人的老大是否相等,只需用

    if(find(a)==find(b))

    即可

    并查集支持的操作

    并查集的数据结构记录了一组分离的动态集合S={S1,S2,…,Sk}。每个集合通过一个代表加以识别

    代表即该元素中的某个元素,哪一个成员被选做代表是无所谓的,重要的是:如果求某一动态集合的代表两次

    且在两次请求间不修改集合,则两次得到的答案应该是相同的。 动态集合中的每一元素是由一个对象来表示的,设x表示一个对象

    并查集的实现需要支持如下操作:   MAKE(x):建立一个新的集合,其仅有的成员(同时就是代表)是x。

    由于各集合是分离的,要求x没有在其它集合中出现过。   

    UNIONN(x,y):将包含x和y的动态集合(例如Sx和Sy)合并为一个新的集合,假定在此操作前这两个集合是分离的。

    结果的集合代表是Sx∪Sy的某个成员。一般来说,在不同的实现中通常都以Sx或者Sy的代表作为新集合的代表。

    此后,由新的集合S代替了原来的Sx和Sy。 FIND(x):返回一个指向包含x的集合的代表

    上代码:

    #include<cstdio>
    #include<cstring>
    using namespace std;
    #define init for(int i=1;i<=n;i++) fa[i]=i
    const int maxn=2e4;
    int fa[maxn],n,m,x,y,z;
    inline int read()
    {
        int S(0); char c=getchar();
        while(c>'9'||c<'0') c=getchar();
        while(c>='0'&&c<='9') S=S*10+c-'0',c=getchar();
        return S;
    }
    int find(int x){return fa[x]==x?fa[x]:fa[x]=find(fa[x]);}
    int main()
    {
        n=read(); m=read();
        init;
        for(int i=1;i<=m;i++)
        {
            z=read();
            if(z==1)
            {
                x=read(); y=read();
                int a=find(fa[x]),b=find(fa[y]);
                if(a!=b) fa[a]=b;
            }
            else
            {
                x=read(); y=read();
                if(find(fa[x])==find(fa[y])) printf("Y
    ");
                else printf("N
    ");
            }
        }
        return 0;
    }

    求无向图的连通分量

    这里提出来强调一下

    因为求无向图连通分量是个非常常用的算法。

    通过并查集可以使得空间上省去对边的保存,同时时间效率又是很高的。   

    需要特别指出的是,如果用链表来实现的话,最后任何在同一个集合(即连通块)中的元素,其代表指针的值都是相等的。

    而采用有根树来实现的话,算法结束后,留下的依然是树的关系,因此如果希望每个元素都指向它的根的话

    还需要对每个节点进行一次find操作,这样每个节点的父节点都是代表此集合的节点。在某些统计问题中,往往需要这样做

    例题2:

    洛谷P1195

    传送门

    题解:

    #include<bits/stdc++.h>
    #define max 1005
    using namespace std;
    int n,m,k,x,y,l,sum,ans;
    int t[max];
    struct Edge
    {
        int u,v,w;
    }edge[max*10];
    bool operator <(Edge a,Edge b)
    {
            return a.w<b.w;
    }
    int find(int x)
    {
        return t[x]==x?t[x]:t[x]=find(t[x]);
    }
    int main()
    {
        scanf("%d%d%d",&n,&m,&k);
        for(int i=1;i<=n;i++) 
        t[i]=i;
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
        }
        sort(edge+1,edge+m+1);
        for(int i=1;i<=m;i++)
        {
            int fx=find(edge[i].u),fy=find(edge[i].v);
            if(fx!=fy)
            {
                t[fx]=fy;
                sum++;
                ans+=edge[i].w;
            }
            if(sum==n-k)
            {
                printf("%d",ans);
                return 0;
            }
        }
        puts("No Answer");
        return 0;
    }
  • 相关阅读:
    C# 事件的简单例子
    pl sql 的目录 所在的目录 不能有 小括号,如 Program Files (x86)
    转】 C# 图片格式(JPG,BMP,PNG,GIF)等转换为ICO图标
    TQQ2440第三节:串口
    今天发现一个bug,不知道是什么问题,printf的问题吗,还是什么。先记下!
    【转载】内存对齐详解
    TQQ2440第二节:流水灯
    TQQ2440第一节:启动代码
    wince下sources\sources.cmn\Makefile.def的相关作用
    (基于Java)编写编译器和解释器第10章:类型检查第一部分
  • 原文地址:https://www.cnblogs.com/U58223-luogu/p/9561750.html
Copyright © 2020-2023  润新知