.并查集.

并查集就是维护每一个树的过程。

836. 合并集合

一共有 n𝑛 个数,编号是 1∼n1∼𝑛,最开始每个数各自在一个集合中。

现在要进行 m𝑚 个操作,操作共有两种:

  1. M a b,将编号为 a𝑎 和 b𝑏 的两个数所在的集合合并,如果两个数已经在同一个集合中,则忽略这个操作;
  2. Q a b,询问编号为 a𝑎 和 b𝑏 的两个数是否在同一个集合中;

输入格式

第一行输入整数 n𝑛 和 m𝑚。

接下来 m𝑚 行,每行包含一个操作指令,指令为 M a b 或 Q a b 中的一种。

输出格式

对于每个询问指令 Q a b,都要输出一个结果,如果 a𝑎 和 b𝑏 在同一集合内,则输出 Yes,否则输出 No

每个结果占一行。

数据范围

1≤n,m≤1051≤𝑛,𝑚≤105

输入样例:

4 5
M 1 2
M 3 4
Q 1 2
Q 1 3
Q 3 4

输出样例:

Yes
No
Yes
将区间合并成一个集合:

将两个集合合并成一个:

 

  代码:
/*
并查集就是让整个区间用一颗数表示
每次并集的根节点就是整个区间的尾部
整棵大树的根节点就是所有区间最长的尾部
*/

#include<bits/stdc++.h>

using namespace std;

int m,n;

//每一个区间点的指向(指向它的父类)
int p[100050];
//经过此操作可让所有的子叶的指向都指向整个大树的根节点
int find(int x)
{
    /*
    “超级加辈”
    让它的父类(下一指向) 指向祖宗(根节点)
    化祖宗为父类进而压缩路径
    */
    //当p[x]==x时,也就让p[x]指向它的祖宗节点了
    //否则就让p[x]去寻找它的指向
    if(p[x]!=x) p[x]=find(p[x]);
    //返回x的父类
    return p[x];
}

int main()
{
    cin >> m >> n;
    //一开始的指向就是它本身,所以整个大树的根节点就是它本身
    for(int i=1;i<=m;i++) p[i]=i;
    
    while(n--)
    {
        char op;
        int a,b;
        cin >> op >> a >> b;
        
        if(op=='M') 
        //让a祖宗的指向(find(a))指向(p[])b的祖宗(find(b))
        p[find(a)]=find(b);
        else
        {
//如果二者的祖宗相同,那即代表在同一区间
            if(find(a)==find(b)) puts("Yes");
            else puts("No");
        }
    }
    return 0;
}

837. 连通块中点的数量

给定一个包含 n𝑛 个点(编号为 1∼n1∼𝑛)的无向图,初始时图中没有边。

现在要进行 m𝑚 个操作,操作共有三种:

  1. C a b,在点 a𝑎 和点 b𝑏 之间连一条边,a𝑎 和 b𝑏 可能相等;
  2. Q1 a b,询问点 a𝑎 和点 b𝑏 是否在同一个连通块中,a𝑎 和 b𝑏 可能相等;
  3. Q2 a,询问点 a𝑎 所在连通块中点的数量;

输入格式

第一行输入整数 n𝑛 和 m𝑚。

接下来 m𝑚 行,每行包含一个操作指令,指令为 C a bQ1 a b 或 Q2 a 中的一种。

输出格式

对于每个询问指令 Q1 a b,如果 a𝑎 和 b𝑏 在同一个连通块中,则输出 Yes,否则输出 No

对于每个询问指令 Q2 a,输出一个整数表示点 a𝑎 所在连通块中点的数量

每个结果占一行。

数据范围

1≤n,m≤1051≤𝑛,𝑚≤105

输入样例:

5 5
C 1 2
Q1 1 2
Q2 1
C 2 5
Q2 5

输出样例:

Yes
2
3

代码: 

注意未合并之前的每个元素长度初始化唯一

#include<bits/stdc++.h>

using namespace std;

int p[100050],size1[100050];

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

int main()
{
    int m,n;
    cin >> m >> n;
    for(int i=1;i<=m;i++) 
    {
        p[i]=i;
        //一开始每个元素都未联通,则长度为1
        size1[i]=1;
    }
    
    while(n--)
    {
        string op;
        int a,b;
        cin >> op;
        if(op=="C")
        {
            cin >> a >> b;
            if(find(a)==find(b)) continue;
            /*
            大家看y总这段代码时要注意,在C操作时,y总先把a,b的根结点取出来了:a = find(a), b = find(b);
            因此接下来是先将集合a接到集合b下再把a的连通块大小加到b上,
            还是先把a的连通块大小加到b上再操作集合都是可以的,
            如果大家没有提前一步的处理,就必须要先加连通块大小再操作集合,
            否则操作完集合后,a和b的根结点将会重叠,导致输出错误!
            */
            //先取出来即代表将a和b的根节点转化为静态变量,a就是find(a)
            //否则经过集合的操作后,find(a)会指向find(b)导致根节点重叠
            //让根节点a所在的集合数值的数量加到b上去
            size1[find(b)]+=size1[find(a)];
            p[find(a)]=find(b);
            
        }
        else if(op=="Q1")
        {
            cin >> a >> b;
            if(find(a)==find(b)) puts("Yes");
            else puts("No");
        }
        else 
        {
            cin >> a;
            cout << size1[find(a)] << endl;
        }
    }
    
    return 0;
}

 240. 食物链

 a,b,c三类共有两种情况,同类,异类。

异类又分为捕食与被捕食的关系,所以能形成下图所示的杀戮关系

不难发现随便两个动物的关系都可以用它到根节点的距离%3来推断。

若a%3=b%3则a与b为同类

若a%3=1,b%3=2则a吃b

 注意,让根节点被吃,这样距离%3的值才能达到统一

动物王国中有三类动物 A,B,C𝐴,𝐵,𝐶,这三类动物的食物链构成了有趣的环形。

A𝐴 吃 B𝐵,B𝐵 吃 C𝐶,C𝐶 吃 A𝐴。

现有 N𝑁 个动物,以 1∼N1∼𝑁 编号。

每个动物都是 A,B,C𝐴,𝐵,𝐶 中的一种,但是我们并不知道它到底是哪一种。

有人用两种说法对这 N𝑁 个动物所构成的食物链关系进行描述:

第一种说法是 1 X Y,表示 X𝑋 和 Y𝑌 是同类。

第二种说法是 2 X Y,表示 X𝑋 吃 Y𝑌。

此人对 N𝑁 个动物,用上述两种说法,一句接一句地说出 K𝐾 句话,这 K𝐾 句话有的是真的,有的是假的。

当一句话满足下列三条之一时,这句话就是假话,否则就是真话。

  1. 当前的话与前面的某些真的话冲突,就是假话;
  2. 当前的话中 X𝑋 或 Y𝑌 比 N𝑁 大,就是假话;
  3. 当前的话表示 X𝑋 吃 X𝑋,就是假话。

你的任务是根据给定的 N𝑁 和 K𝐾 句话,输出假话的总数。

输入格式

第一行是两个整数 N𝑁 和 K𝐾,以一个空格分隔。

以下 K𝐾 行每行是三个正整数 D,X,Y𝐷,𝑋,𝑌,两数之间用一个空格隔开,其中 D𝐷 表示说法的种类。

若 D=1𝐷=1,则表示 X𝑋 和 Y𝑌 是同类。

若 D=2𝐷=2,则表示 X𝑋 吃 Y𝑌。

输出格式

只有一个整数,表示假话的数目。

数据范围

1≤N≤500001≤𝑁≤50000,
0≤K≤1000000≤𝐾≤100000

输入样例:

100 7
1 101 1 
2 1 2
2 2 3 
2 3 3 
1 1 3 
2 3 1 
1 5 5

输出样例:

3

 当不在一个区域,化为同一区域时:

①同类:

②捕食关系 

 代码:
//两种做法,这里用并查集来维护整个区间
#include<bits/stdc++.h>

using namespace std;

int p[50050],ans[50050];

int find(int x)
{
    //在p[x]指向其根节点的过程中,x到根节点的距离会被逐渐算出来了
    //不递归到最后是不会执行加法语句的,所以是从最上面依次向下执行加法语句
    if(p[x]!=x)
    {
    int u=find(p[x]);
    //算出每一个值到根节点的距离(压缩路径)
    //x到父节点的距离加上父节点到根节点的距离之和即为x到根节点的距离
    /*
    父节点一定指向根节点吗?
    一开始x的父节点不一定指向根节点,但是经过不断的递归
    最终是从根节点的儿子开始回溯
    先是根节点到儿子的距离被加进去,接着是孙子到儿子的距离被加进去
    在是重孙到孙子的距离被加进去......最后加到x的父节点到爷爷的距离被加进去
    由于一开始的ans[x]即为x到父节点的距离,所以递归、追溯完毕之后ans[x]就是x到根节点的距离
    
    */
    ans[x]+=ans[p[x]];
    //让x指向根节点(以便判断 与其他区域是否在同一片区域)
    p[x]=u;
    }
    return p[x];
}
int main()
{
    int m,n;
    cin >> m >> n;
    
    //一开始没形成杀戮领域前都是独立的,自己就是单独一片区域
    for(int i=1;i<=m;i++) p[i]=i;
    
    int ant=0;
    //模拟题目要求
    while(n--)
    {
        int x,a,b;
        cin >> x >> a >> b;
        //先记录下新加入的动物a、b的根节点指向
        int px=find(a),py=find(b);
        
        //“当前的话中 X或 Y比 N大,就是假话;”
        if(a>m||b>m) 
            ant++;
        else 
        {
        if(x==1)
        {
        //判断是否为假语句的前提是px与py在同一个区域里(同一个根节点)
        //若满足是同一类,那么他俩到根节点的距离对3取模都为0
            if(px==py && (ans[a]-ans[b])%3) ant++;
            //如果是同一类而不再同一片区域内,表示之前从来没出现过,那么创建一个区域,b为根节点
            else if(px!=py)
            {
                //让y的根节点作为x与y所在区域的新的根节点
                p[px]=py;
                //(ans[px]+ans[a])%3=ans[b]%3;(同一类)
                /*
                 ans[px]是待计算的距离(因为合并后这段距离未知)。
                 意义是计算出a的根节点到b的根节点的距离
                */
                ans[px]=ans[b]-ans[a];
            }
        }
        //同理
        else 
        {
            if(px==py && (ans[a]-ans[b]-1)%3) ant++;
            else if(px!=py)
            {
                p[px]=py;
                //(ans[x]+ans[px])%3=ans[y]%3+1(x吃y),x比y高一级
                ans[px]=ans[b]+1-ans[a];
            }
        }
        }
    }
    cout << ant;
    return 0;
}

tips

注意递归函数的应用!!!

递归不触碰到某一条件是不会进行追溯的。

并查集很好的运用的树的思想

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值