【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
    评论
4S店客户管理小程序-毕业设计,基于微信小程序+SSM+MySql开发,源码+数据库+论文答辩+毕业论文+视频演示 社会的发展和科学技术的进步,互联网技术越来越受欢迎。手机也逐渐受到广大人民群众的喜爱,也逐渐进入了每个用户的使用。手机具有便利性,速度快,效率高,成本低等优点。 因此,构建符合自己要求的操作系统是非常有意义的。 本文从管理员、用户的功能要求出发,4S店客户管理系统中的功能模块主要是实现管理员服务端;首页、个人中心、用户管理、门店管理、车展管理、汽车品牌管理、新闻头条管理、预约试驾管理、我的收藏管理、系统管理,用户客户端:首页、车展、新闻头条、我的。门店客户端:首页、车展、新闻头条、我的经过认真细致的研究,精心准备和规划,最后测试成功,系统可以正常使用。分析功能调整与4S店客户管理系统实现的实际需求相结合,讨论了微信开发者技术与后台结合java语言和MySQL数据库开发4S店客户管理系统的使用。 关键字:4S店客户管理系统小程序 微信开发者 Java技术 MySQL数据库 软件的功能: 1、开发实现4S店客户管理系统的整个系统程序; 2、管理员服务端;首页、个人中心、用户管理、门店管理、车展管理、汽车品牌管理、新闻头条管理、预约试驾管理、我的收藏管理、系统管理等。 3、用户客户端:首页、车展、新闻头条、我的 4、门店客户端:首页、车展、新闻头条、我的等相应操作; 5、基础数据管理:实现系统基本信息的添加、修改及删除等操作,并且根据需求进行交流信息的查看及回复相应操作。
现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本微信小程序医院挂号预约系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息,使用这种软件工具可以帮助管理人员提高事务处理效率,达到事半功倍的效果。此微信小程序医院挂号预约系统利用当下成熟完善的SSM框架,使用跨平台的可开发大型商业网站的Java语言,以及最受欢迎的RDBMS应用软件之一的MySQL数据库进行程序开发。微信小程序医院挂号预约系统有管理员,用户两个角色。管理员功能有个人中心,用户管理,医生信息管理,医院信息管理,科室信息管理,预约信息管理,预约取消管理,留言板,系统管理。微信小程序用户可以注册登录,查看医院信息,查看医生信息,查看公告资讯,在科室信息里面进行预约,也可以取消预约。微信小程序医院挂号预约系统的开发根据操作人员需要设计的界面简洁美观,在功能模块布局上跟同类型网站保持一致,程序在实现基本要求功能时,也为数据信息面临的安全问题提供了一些实用的解决方案。可以说该程序在帮助管理者高效率地处理工作事务的同时,也实现了数据信息的整体化,规范化与自动化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值