首页 > 其他分享 >并查集(nuist LevOJ P1648)

并查集(nuist LevOJ P1648)

时间:2023-04-01 16:11:47浏览次数:56  
标签:取模 int P1648 查集 丹方 稳定剂 nuist 药材

一、并查集

1.1 并查集简介

并查集是一种简单的集合表示,是一种树形数据结构,可处理不相交集合的合并及查询问题。并查集可求联动分支数。

并查集存储:

现有9个元素0~9,建立一个数组(初始化元素为-1),用数组下标表示元素数组中的数据表示根节点的下标。数组中数据为负数时表示它是根节点。

并查集支持以下3种操作:

  1. Initial(S):将集合S中每个元素都初始化为只有一个单元素的子集合。
  2. Union(S,Root1,Root2):把集合S中的子集合Root2并入子集合Root1.要求Root1和Root2互不相交,否则不执行合并。
  3. Find(S,x):查找集合S中单元素x所在的子集合,并返回该子集合的根节点。

2.2 并查集实现

#define SIZE 100
int UFSets[SIZE];//集合元素数组
//Initial操作
void Initial(int S[])
{
    for(int i=0;i<SIZE;i++)
        s[i]=-1;
}
//Find操作
int Find(int S[],int x)
{
    while(S[x]>=0)//寻找x的根结点
        x=S[x];
    return x;
}
//Union操作
void Union(int S[],int Root1,int Root2)
{
    if(Root1==Root2)//要求Root1和Root2是不同的集合
        return;
    S[Root2]=Root1;//将根Root2连接到另一根Root1下面
}

二、题解(LevOJ P1648 炼丹术)

注意:这里根节点元素值是它自身下标,不是-1

/*
P1648 炼丹术
====关键词===================================================
1.暴力:求幂集,判断是否符合标准,计数
2.单方和其稳定剂构成有向图,(由于输入是全排列,故图必然由若干个环组成),找图中有几个环,再求(幂集数-1)(去掉空集)
    2.1 第一次没过,因为要在mypow中对结果取模(不用开longlong),而非最后对ans取模(过不了)
3.模板:并查集(根节点元素值是它自身下标,不是-1)
====关键词===================================================
====题目===================================================
题目描述
三水最近在学习炼丹术。但是众所周知炼丹术是一门危险的学科,需要大量的调参才能保证安全。好在三水在洗衣机里面找到了一张失传已久的图纸,里面记录了若干种材料的药性。这张图纸上记录了n 种不同的药材,对于每种药材,都需要恰好一种药材来使其稳定 (这种药材可能是其自身,即这种药材本身就很稳定)。三水想知道,通过这张图纸,可以得到多少种不同的稳定的丹方。保证每种药材只会作为稳定剂出现一次。
我们认为一个丹方是从n 种药材中选择若干种 (不为0 ),两个丹方被认为是不同的当且仅当存在一种药材在其中一个丹方中且不在另一个中。我们称一个丹方是稳定的,当且仅当所有出现在丹方中的药材的稳定剂也在药材中。
因为输出结果可能很大,所以答案对 998244353 取模。
输入描述
第一行一个数字n , 表示有n(1⩽n⩽10^6) 种不同的药材。 接下来一行n 个数字,第i 数字ai​(1⩽ai⩽n) 表示药材i 的稳定剂是ai,保证输入是1 到n 的一个全排列。
输出描述
一个整数
n ,表示答案对 998244353 取模的结果。
样例输入
Copy to Clipboard
6
2 3 4 5 6 1
样例输出
Copy to Clipboard
1
====题目===================================================
*/
#include <iostream>
#include <cmath>
using namespace std;
const int N = 1e6 + 10;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;
int n;      //几个结点
int a[N];   //结点稳定剂
int pre[N]; //并查集
int cnt;    //环数
int ans;    //结果
int mypow(int x)
{ // 求2的x次方(由于取了模,不需要开longlong)
    int ret = 1;
    while (x--)
    {
        ret = (ret * 2) % MOD; //结果取模
    }
    return ret;
}
int find(int x)
{ //寻找x的根节点(若该结点已经是根节点,则返回他自身)
    while (x != pre[x])
    {
        x = pre[x] = pre[pre[x]]; //路径压缩
        // x=pre[x];//不路径压缩
    }
    return x;
}
void show_pre()
{
    cout << "pre ";
    for (int i = 1; i <= n; i++)
    {
        cout << pre[i] << " ";
    }
    cout << endl;
}
int main()
{
    //读数
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
        pre[i] = i; //初始化查数组
    }
    //题解
    // show_pre();
    for (int i = 1; i <= n; i++)
    {
        int u = find(i), v = find(a[i]); //通过前缀数组更新并查集,查询过程进行路径压缩
        // printf("u=%d,v=%d\n", u, v);
        if (u != v)
        {
            // printf("pre[%d]=%d\n", u, v);
            pre[u] = v; //合并关联的集合
        }
    }
    // show_pre();
    // find(1);
    // show_pre();
    for (int i = 1; i <= n; ++i)
    { //记录不同集合个数
        if (pre[i] == i)
            cnt++;
    }
    //输出答案
    printf("%d", mypow(cnt) - 1);
    return 0;
}

三、自己写的暴力题解(可对比下)

/*
P1648 炼丹术
====关键词===================================================
1.暴力:求幂集,判断是否符合标准,计数
2.单方和其稳定剂构成有向图,(由于输入是全排列,故图必然由若干个环组成),找图中有几个环,再求(幂集数-1)(去掉空集)
    2.1 第一次没过,因为要在mypow中对结果取模(不用开longlong),而非最后对ans取模(过不了)
3.模板:并查集
====关键词===================================================
====题目===================================================
题目描述
三水最近在学习炼丹术。但是众所周知炼丹术是一门危险的学科,需要大量的调参才能保证安全。好在三水在洗衣机里面找到了一张失传已久的图纸,里面记录了若干种材料的药性。这张图纸上记录了n 种不同的药材,对于每种药材,都需要恰好一种药材来使其稳定 (这种药材可能是其自身,即这种药材本身就很稳定)。三水想知道,通过这张图纸,可以得到多少种不同的稳定的丹方。保证每种药材只会作为稳定剂出现一次。
我们认为一个丹方是从n 种药材中选择若干种 (不为0 ),两个丹方被认为是不同的当且仅当存在一种药材在其中一个丹方中且不在另一个中。我们称一个丹方是稳定的,当且仅当所有出现在丹方中的药材的稳定剂也在药材中。
因为输出结果可能很大,所以答案对 998244353 取模。
输入描述
第一行一个数字n , 表示有n(1⩽n⩽10^6) 种不同的药材。 接下来一行n 个数字,第i 数字ai​(1⩽ai⩽n) 表示药材i 的稳定剂是ai,保证输入是1 到n 的一个全排列。
输出描述
一个整数
n ,表示答案对 998244353 取模的结果。
样例输入
Copy to Clipboard
6
2 3 4 5 6 1
样例输出
Copy to Clipboard
1
====题目===================================================
*/
#include <iostream>
#include <cmath>
using namespace std;
const int N = 1e6 + 10;
const int MOD = 998244353;
const int INF = 0x3f3f3f3f;
int n;           //几个结点
int a[N];        //结点稳定剂
bool visited[N]; //结点是否被访问
int ring;        //环数
int ans;         //结果
int mypow(int x)
{ // 求2的x次方(由于取了模,不需要开longlong)
    int ret = 1;
    while (x--)
    {
        ret = (ret * 2) % MOD; //结果取模
    }
    return ret;
}
void find_ring()
{
    int ss, s, e;
    //从第一个结点开始
    for (int i = 1; i <= n; i++)
    {
        if (!visited[i])
        {
            ring++; //环数+1
            ss = i;
            s = i;
            e = a[s];
            visited[i] = true;
            while (ss != e)
            { //将处于同一个环的结点都标记上
                s = e;
                e = a[s];
                visited[s] = true;
            }
        }
    }
}
int main()
{
    //读数
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
        scanf("%d", &a[i]);
    }
    //题解
    find_ring();
    ans = mypow(ring) - 1;
    //输出答案
    printf("%d", ans);
    return 0;
}

参考

王道数据结构考研书

https://www.cnblogs.com/chanxe/p/16270304.html

https://blog.csdn.net/qq_57150526/article/details/124769313

标签:取模,int,P1648,查集,丹方,稳定剂,nuist,药材
From: https://www.cnblogs.com/FishSmallWorld/p/17278400.html

相关文章

  • 算法笔记之并查集
    一、并查集的定义并查集是一种树型的数据结构,用于处理一些不相交集合(disjointsets)的合并及查询问题。常常在使用中以森林来表示。并查集,顾名思义,支持以下两种操作操作:并(Union):把两个不相交的集合合并为一个集合。查(Find):查询两个元素是否在同一个集合中。二、并查......
  • The Suspects POJ - 1611 (并查集)
    题意:n个学生分属m个团体,一个学生可以属于多个团体。一个学生疑似患病则它所属的整个团体都疑似患病。已知0号学生疑似患病,以及每个团体都由哪些学生构成,求一共多少个学生疑似患病。分析:维护一个并查集,查询与0在同一集合的元素数量。#include<iostream>#include<cstdio>using......
  • 天梯赛练习题 L3-003 社交集群 (简单并查集)
    https://pintia.cn/problem-sets/994805046380707840/exam/problems/994805053141925888题目大意:当你在社交网络平台注册时,一般总是被要求填写你的个人兴趣爱好,以便找到......
  • 并查集
    并查集主要包括初始化、寻根以及合并三个操作。例如a、b、c、d、e,初始化他们的祖先为本身。寻根操作:intfind_root(intx){//非路径压缩returnx==s[x]?x:finde_r......
  • How Many Tables HDU - 1213(并查集/连通块数量)
    题意:朋友的朋友是朋友如果A认识B,B认识C,那么ABC三个人就可以坐在同一张桌子上但如果A认识B,C认识D,那我们就默认AB和CD不认识,需要准备两张桌子现在我们需要你计算出,我们一......
  • 2023-03-23_并查集
    并查集两个点之间在树或图中是否连通的问题。1什么是并查集?连接问题网络中节点间的连接状态数学中的集合类实现连接问题与路径问题:解决路径问题便一定可以解......
  • 并查集模板
    并查集(Union(并),Find(查),Set(集))一般用树的形式保存集合,但是是用数组模拟的树对于并查集树上的所有点,只有根结点是p[x]=x的,其他的p[x]都是父结点那么就可以通过whil......
  • 并查集拓展
    上一期由于上一期过水,只讲了一点点序列的问题,然而在乱逛看题解的时候,发现其实并查集能做到的远远不止图论与有限步骤序列问题,今天就从一(不会讲模板的)开始来记录一下并查集......
  • TZOJ 5784: 团伙 并查集
    描述 在某城市里住着n个人,任何两个认识的人不是朋友就是敌人,而且满足:1、我朋友的朋友是我的朋友;2、我敌人的敌人是我的朋友;所有是朋友的人组成一个团伙。告诉你关于......
  • ABC 293 ABCD(并查集)
    A-SwapOddandEven#include<bits/stdc++.h>usingnamespacestd;typedeflonglongLL;typedefpair<LL,LL>PII;constLLMAXN=1e18,MINN=-1e18;constLLN=1e6......