【Acwing提高】并查集

【Acwing提高】并查集

知识点

题目知识点
格子游戏并查集判环
搭配购买并查集维护集合大小+01背包
程序自动分析并查集判冲突+离散化
银河英雄传说并查集维护距离,边带权
奇偶游戏离散化,奇偶转换,边带权/扩展域

题目

格子游戏

思路
裸的并查集不用维护什么东西,点与点成环游戏结束,即构建关系的时候在同一集合里。
这里有个技巧,并查集维护一个数比较方便,所以把二维坐标利用x*n+y转化成二维的(注意要让输入也自减,保证从0开始)
代码

#include<bits/stdc++.h>
using namespace std;
const int N=4e4+10;
int n,m;
int p[N];

int get(int x,int y)
{
    return x*n+y;
}
int find(int x)
{
    if(p[x]!=x)p[x]=find(p[x]);
    return p[x];
}
int main()
{
    cin>>n>>m;
    for(int i=0;i<n*n;i++)p[i]=i;
    
    int res=0;
    for(int i=1;i<=m;i++)
    {
        int x,y;
        char d;
        cin>>x>>y>>d;
        x--;y--;
        int a=get(x,y);
        int b;
        if(d=='D')b=get(x+1,y);
        else b=get(x,y+1);
        
        int pa=find(a),pb=find(b);
        if(pa==pb)
        {
            res=i;
            break;
        }
        p[pa]=pb;
    }
    if(!res)puts("draw");
    else cout<<res<<endl;
    
    return 0;
}

搭配购买

思路
由于是无向图的关系(并查集主要处理无向图,有向图难搞(传递闭包))所以可以直接把连通块作为物品,可以对比一下有依赖的背包

先把有边相连的放到一个集合,把每个连通块作为一个物品做01背包

并查集要维护集合价值和体积的总和(绑定到祖宗节点上)
代码

#include<bits/stdc++.h>
using namespace std;

const int N=1e5+10;
int n,m,vol;
int v[N],w[N];
int p[N];
int f[N];
int find(int x)
{
    if(p[x]!=x)return p[x]=find(p[x]);
    return p[x];
}
int main()
{
    cin>>n>>m>>vol;
    
    for(int i=1;i<=n;i++)p[i]=i;
    for(int i=1;i<=n;i++)cin>>v[i]>>w[i];
    
    while(m--)
    {
        int a,b;
        cin>>a>>b;
        int pa=find(a),pb=find(b);
        if(pa!=pb)
        {
            v[pb]+=v[pa];
            w[pb]+=w[pa];
            p[pa]=p[pb];
        }
    }
    
    //01背包
    for(int i=1;i<=n;i++)
        if(p[i]==i)//只有是根节点
            for(int j=vol;j>=v[i];j--)
                f[j]=max(f[j],f[j-v[i]]+w[i]);
    cout<<f[vol];
    return 0;
}

程序自动分析

思路
数据范围太大:需要离散化处理

由于约数条件顺序无所谓,所以先处理所有相等条件,相等条件直接一定不会出现矛盾。不等条件,如果xi和xj在同一集合当作,则已经矛盾。

1离散化
2将所有相等条件合并(并查集合并)
3依次判断每个不等条件(并查集查询)

代码

#include<bits/stdc++.h>
using namespace std;
const int N=2e6+10;

int n,m;
int p[N];
unordered_map<int,int>S;
struct Query
{
    int x,y,e;
}query[N];

int get(int x)//离散化
{
    if(S.count(x)==0)S[x]=++n;
    return S[x];
}
int find(int x)
{
    if(p[x]!=x)return p[x]=find(p[x]);
    return p[x];
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        n=0;
        S.clear();
        scanf("%d",&m);
        for(int i=0;i<m;i++)
        {
            int x,y,e;
            scanf("%d%d%d",&x,&y,&e);
            query[i]={get(x),get(y),e};//离散化
        }
        
        for(int i=1;i<=n;i++)p[i]=i;
        
        for(int i=0;i<m;i++)
            if(query[i].e==1)//合并相等条件
            {
                int pa=find(query[i].x),pb=find(query[i].y);
                p[pa]=pb;
            }
        bool has_conflict=0;
        for(int i=0;i<m;i++)//判不等条件
            if(query[i].e==0)
            {
                int pa=find(query[i].x),pb=find(query[i].y);
                if(pa==pb)
                {
                    has_conflict=1;
                    break;
                }
            }
        if(has_conflict)puts("NO");
        else puts("YES");
    }    
    return 0;
}

银河英雄传说

思路
并查集维护距离
并查集的思想是以祖宗节点为代表,那么间隔舰数位
m a x ( 0 , a b s ( d [ x ] − d [ y ] ) − 1 ) max(0,abs(d[x]-d[y])-1) max(0,abs(d[x]d[y])1)
d [ x ] d[x] d[x]为x到祖宗节点距离,初值为0。那么我们如何维护它呢?
在这里插入图片描述
队列P:B到队头A的距离为d[B]
队列Q:C到队头D的共有s[D]个元素
将P接到Q下面,要维护更新P中各节点到新祖宗节点的距离,则每个节点加上s[D],实际上要实现这一操作只需令P原来的祖宗节点距离更新为s[D],这样在find查询的时候就会更新,其他节点到新祖宗节点的距离,需要注意的是,我们还需要在更新距离后维护合并集合的size

只是合并,还没有查询的状态
在这里插入图片描述
在这里插入图片描述

代码

#include<bits/stdc++.h>
using namespace std;

const int N=3e5+10;
int m;
int p[N],s[N],d[N];
int find(int x)
{
    if(p[x]!=x)
    {//回溯更新
        int root=find(p[x]);
        d[x]+=d[p[x]];//节点到祖宗节点距离=节点到父节点距离+父节点到祖宗节点距离
        p[x]=root;
    }
    return p[x];
}
int main()
{
    scanf("%d",&m);
    for(int i=1;i<N;i++)
    {
        p[i]=i;
        s[i]=1;
    }
    while(m--)
    {
        char op[2];
        int a,b;
        scanf("%s%d%d",op,&a,&b);
        if(op[0]=='M')
        {
            int pa=find(a),pb=find(b);
            d[pa]=s[pb];//其实相当于懒惰标记,后面更新有点像差分前缀和
            s[pb]+=s[pa];
            p[pa]=pb;
        }
        else
        {
            int pa=find(a),pb=find(b);
            if(pa!=pb)puts("-1");
            else printf("%d\n",max(0,abs(d[a]-d[b])-1));
        }
    }
    return 0;
}

奇偶游戏

思路
s[l~r]有奇数个1=s[r]-s[l-1]有奇数个1=s[r]和s[l-1]奇偶相同,反之不同
有没有撒谎就看有没有产生矛盾

带边权做法:
在这里插入图片描述

代码

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;//1e5个点可能由2e5个关系
int n,m;
int p[N],d[N];
unordered_map<int,int>S;

int get(int x)
{
    if(!S.count(x))S[x]=++n;
    return S[x];
}
int find(int x)
{
    if(p[x]!=x)
    {
        int root=find(p[x]);
        d[x]+=d[p[x]];
        d[x]%=2;//mod2意义下距离,如果不是mod2下面主函数维护距离也要这样
        p[x]=root;
    }
    return p[x];
}
struct Input
{
    int x,y,dis;
}In[N];
int main()
{
    cin>>n>>m;
    n=0;
    int res=m;
    for(int i=1;i<=m;i++)
    {
        int a,b,dis;
        string op;
        cin>>a>>b>>op;
        a--;
        if(op=="even")dis=0;
        else dis=1;
        In[i]={get(a),get(b),dis};
    }
    
    for(int i=1;i<=n;i++)p[i]=i;
    
    for(int i=1;i<=m;i++)
    {
        int x=In[i].x,y=In[i].y;
        if(In[i].dis)//异类
        {
            int px=find(x),py=find(y);
            if(px==py)
            {
                if((d[x]^d[y])==0)//别写成d[px]^d[py]
                {
                    res=i-1;
                    break;
                }
            }
            else
            {
                d[px]=d[x]^d[y]^1;//维护更新距离但和siz无光的例子
                p[px]=py;
            }
        }
        else//同类
        {
            int px=find(x),py=find(y);
            if(px==py)
            {
                if(d[x]^d[y])
                {
                    res=i-1;
                    break;
                }
            }
            else
            {
                d[px]=d[x]^d[y]^0;
                p[px]=py;
            }
        }
    }
    cout<<res;
    
    return 0;
}

扩展域解法
在这里插入图片描述
扩展域的元素是某一条件,当这些元素处于同一集合的时候,说明这些条件是可以互相推导出来的
代码

#include<bits/stdc++.h>
using namespace std;
const int N=4e5+10;
int n,m;
int p[N];//2个扩展域开两倍
unordered_map<int,int>S;

int get(int x)
{
    if(!S.count(x))S[x]=++n;
    return S[x];
}
int find(int x)
{
    if(p[x]!=x)return p[x]=find(p[x]);
    return p[x];
}
struct Input
{
    int x,y,dis;
}In[N];
int main()
{
    cin>>n>>m;
    n=0;
    int res=m;
    for(int i=1;i<=m;i++)
    {
        int a,b,dis;
        string op;
        cin>>a>>b>>op;
        a--;
        if(op=="even")dis=0;
        else dis=1;
        In[i]={get(a),get(b),dis};
    }
    
    for(int i=1;i<=2*n;i++)p[i]=i;//开两倍初始化
    
    for(int i=1;i<=m;i++)
    {
        int x=In[i].x,y=In[i].y;
        int px=find(x),py=find(y),pxn=find(x+n),pyn=find(y+n);
        if(In[i].dis)
        {
            if(px==py||pxn==pyn)//先判矛盾
            {
                res=i-1;
                break;
            }
            else 
            {
                if(px!=pyn)p[px]=pyn;
                if(py!=pxn)p[py]=pxn;
            }
        }
        else
        {
            if(px==pyn||py==pxn)
            {
                res=i-1;
                break;
            }
            else
            {
                if(px!=py)p[px]=py;
                if(pxn!=pyn)p[pxn]=pyn;
            }
        }
    }
    cout<<res;
    
    return 0;
}

总结

并查集主要用于解决无向图,题目常常给出具有双向传递性的关系,并且这些关系可以相互推导。我们首先要挖掘关系之间是如何传递的,再利用并查集解决问题。

边带权:把信息蕴含到边权之中,处于同一集合的元素表明具有关系
扩展域:把条件本身作为元素,处于同一集合的元素表明对应的条件可以相互推导

数据处理:离散化,N个点要开2×N p[x]数组,扩展域在2XN基础上再×扩展域数量(初始化的时候也别忘记)

信息维护
维护集合大小,当单个体积为1时,体积=个数
维护距离(边权),通常更新距离更新在每个集合祖宗节点(相当于lazy标记,或者差分),在find的时候子节点会随之更新。需要注意的是距离的维护未必和体积相关,需要对应题目来变化

细节:如果做边带权去做取模运算,需要注意main函数和find函数边权更新的时候都要取模,如果涉及减法运算需要加上模数防止出现负数

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。在编写C程序时,需要注意变量的声明和定义、指针的使用、内存的分配与释放等问题。C语言中常用的数据结构包括: 1. 数组:一种存储同类型数据的结构,可以进行索引访问和修改。 2. 链表:一种存储不同类型数据的结构,每个节点包含数据和指向下一个节点的指针。 3. 栈:一种后进先出(LIFO)的数据结构,可以通过压入(push)和弹出(pop)操作进行数据的存储和取出。 4. 队列:一种先进先出(FIFO)的数据结构,可以通过入队(enqueue)和出队(dequeue)操作进行数据的存储和取出。 5. 树:一种存储具有父子关系的数据结构,可以通过中序遍历、前序遍历和后序遍历等方式进行数据的访问和修改。 6. 图:一种存储具有节点和边关系的数据结构,可以通过广度优先搜索、深度优先搜索等方式进行数据的访问和修改。 这些数据结构在C语言中都有相应的实现方式,可以应用于各种不同的场景。C语言中的各种数据结构都有其优缺点,下面列举一些常见的数据结构的优缺点: 数组: 优点:访问和修改元素的速度非常快,适用于需要频繁读取和修改数据的场合。 缺点:数组的长度是固定的,不适合存储大小不固定的动态数据,另外数组在内存中是连续分配的,当数组较大时可能会导致内存碎片化。 链表: 优点:可以方便地插入和删除元素,适用于需要频繁插入和删除数据的场合。 缺点:访问和修改元素的速度相对较慢,因为需要遍历链表找到指定的节点。 栈: 优点:后进先出(LIFO)的特性使得栈在处理递归和括号匹配等问题时非常方便。 缺点:栈的空间有限,当数据量较大时可能会导致栈溢出。 队列: 优点:先进先出(FIFO)的特性使得
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。下面详细介绍C语言的基本概念和语法。 1. 变量和数据类型 在C语言中,变量用于存储数据,数据类型用于定义变量的类型和范围。C语言支持多种数据类型,包括基本数据类型(如int、float、char等)和复合数据类型(如结构体、联合等)。 2. 运算符 C语言中常用的运算符包括算术运算符(如+、、、/等)、关系运算符(如==、!=、、=、<、<=等)、逻辑运算符(如&&、||、!等)。此外,还有位运算符(如&、|、^等)和指针运算符(如、等)。 3. 控制结构 C语言中常用的控制结构包括if语句、循环语句(如for、while等)和switch语句。通过这些控制结构,可以实现程序的分支、循环和多路选择等功能。 4. 函数 函数是C语言中用于封装代码的单元,可以实现代码的复用和模块化。C语言中定义函数使用关键字“void”或返回值类型(如int、float等),并通过“{”和“}”括起来的代码块来实现函数的功能。 5. 指针 指针是C语言中用于存储变量地址的变量。通过指针,可以实现对内存的间接访问和修改。C语言中定义指针使用星号()符号,指向数组、字符串和结构体等数据结构时,还需要注意数组名和字符串常量的特殊性质。 6. 数组和字符串 数组是C语言中用于存储同类型数据的结构,可以通过索引访问和修改数组中的元素。字符串是C语言中用于存储文本数据的特殊类型,通常以字符串常量的形式出现,用双引号("...")括起来,末尾自动添加'\0'字符。 7. 结构体和联合 结构体和联合是C语言中用于存储不同类型数据的复合数据类型。结构体由多个成员组成,每个成员可以是不同的数据类型;联合由多个变量组成,它们共用同一块内存空间。通过结构体和联合,可以实现数据的封装和抽象。 8. 文件操作 C语言中通过文件操作函数(如fopen、fclose、fread、fwrite等)实现对文件的读写操作。文件操作函数通常返回文件指针,用于表示打开的文件。通过文件指针,可以进行文件的定位、读写等操作。 总之,C语言是一种功能强大、灵活高效的编程语言,广泛应用于各种领域。掌握C语言的基本语法和数据结构,可以为编程学习和实践打下坚实的基础。
该资源内项目源码是个人的课程设计、毕业设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。 该资源内项目源码是个人的课程设计,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! ## 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值