并查集讲解 + AcWing 1250. 格子游戏(并查集判环 )

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

并查集的回顾:

并查集树的形式维护所有的集合(不一定是二叉树),每一个集合的编号是根节点的编号。对于每个点x我们都存储一下它的父节点p[x]

并查集的操作:(三点核心)

一、初始化:

初始化将每个点的父节点设为自己即可。

O(n)

代码实现:

void init(int n)
{
        for(int i=1;i<=n;++i)//下标是0是1灵活变化即可
        {
                p[i] = i;
        }
}

下面两个并查集基本操作都是近乎O(1)

二、并

如果两个点不属于一个集合,将其中一个点的根节点的父节点指向另一个点的根节点即可。

代码实现:

void _union(int a,int b)
{
    int pa = find(a);//a的根节点
    int pb = find(b);//b的根节点
    p[pa] = pb;//a根节点的父节点指向b的根节点
}

三、查:

当我们查询某个点属于哪个集合的时候(换句话说就是查询属于的是哪个树根),我们可以先找到这个点的father,判断其father是否是树根,如果不是树根(if(p[x]!=x))再往上找,直到找到为止,这样我们就找到了树根 。这是一个递归的过程在回溯的时候我们可以顺便进行优化,将除了根节点外路径上的每个节点的父节点p[x]都指向树根,这就完成了路径压缩优化。

代码实现:

int find(int x)
{
        if(p[x]!=x) p[x] = find(p[x]);
        return p[x];
}

题意:

在一个一个 n×n 的点阵中,两个人轮流在相邻的点之间画上红边和蓝边,直到围成一个封闭的圈(面积不必为 1)为止,“封圈”的那个人就是赢家。但这个题目并不是问我们谁是赢家,只是问:在第几步的时候游戏结束,如果输入的指定步数上限 m 步之后也没有结束,则输出一行“draw”(平局)。

思想:

并查集判环:任意一个图中,增加一条边形成环当且仅当这条边连接的两点已经连通,于是可以将点分为若干个集合每个集合对应图中的一个连通块

对于并查集我们一般更习惯进行一维操作,对于题中的二维坐标(x, y)我们通过坐标变换为一维坐标即可,变换方式:x*n + y(下标从0开始,n表示矩阵边的大小)

因此:

我们可以把每一个点看做图当中的点,把每一条边看做图中的边。当我们发现图中出现环的时候,就等价于这两个点在连边之前,就已经在同一个联通块当中了

所以说每一步是否会形成环,就等价于是说这两个点在连边之前有没有在集合里。(解题核心在这里)

①如果已经出现过了,就代表会形成环。

②否则,就不会形成环。

这样,我们就可以使用路径压缩的并查集了。

时间复杂度:

首先并查集这个数据结构能够在近乎O(1)的时间复杂度内快速支持合并和查询两个操作。

之后我们可以计算本题的时间复杂度为O(mlogn)(其中logn的很小,因为并查集的常数非常小)

代码+注释:

#include<bits/stdc++.h>

using namespace std;
int n, m;
int x, y;
char op;
const int N = 200*200+10;
int p[N];

void init(int n)
{
        for(int i=0;i<n;++i)
        {
                p[i] = i;
        }
}

int find(int x)
{
        if(p[x]!=x) p[x] = find(p[x]);
        return p[x];
}

int get(int x, int y)
{
        return x*n + y;
}

int main()
{
        cin>>n>>m;
        init(n*n);//从n*n的矩阵降为成n*n的一维数组p
        int ans = 0;
        for(int i=1;i<=m;++i)//枚举所有操作,通过并查集操作判断a和b是否在同一个集合,
        {
                cin>>x>>y>>op;
                --x, --y;//由于输入的坐标都是以1开始的,为了能够用get函数中的转化公式,我们要先减一化成以0开始的坐标
                int a, b;
                a = get(x, y);
                if(op=='D') b = get(x+1, y);
                else b = get(x, y+1);
                int pa = find(a), pb = find(b);
                if(pa!=pb) p[pa] = pb;//若不在同一个集合,则需要将两个集合进行合并
                else//若在同一个集合则标记此操作可以让格子形成环
                {
                    //任意一个图中,增加一条边形成环当且仅当这条边连接的两点已经联通
                        ans = i;
                        break;
                }
        }
        if(!ans) cout<<"draw"<<endl;//ans的值不变说明是平局,未成环
        else cout<<ans<<endl;

        return 0;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值