幼儿园的战争

本文介绍如何使用并查集算法解决一个关于幼儿园孩子间帮派形成与变动的问题,包括孩子间的打架、诱惑及反叛等操作,并提供详细的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

幼儿园的孩子们正在做游戏,每个人都有自己的帮派,帮派之间打架,然后赢者吞并弱者扩大自己的势力。最开始每个孩子的帮派中只有自己,然后接下来有会有两个人打架,这两个人会集结自己所属的势力开始打架,打赢的一方就会吞并输的一方,当然如果x,y是一个势力就不会打起来。有些聪明的小朋友会将自己的糖分给其他小朋友引诱他离开所属势力加入到自己势力。又有一些小朋友会对现在的势力不满,然后反叛出去自立门户。

作为打架的双方,只有人数大于另一方才能打赢。即:人数相等则没有输赢,两个帮派没有变化。

幼儿园里面共有N个孩子,接下来有M次操作,操作有如下4种
1)  query      查询现在有多少个势力。
2)  fight x y  表示x,y打架.并输出"z is winner!"胜利的一方(z是x或y),如果没有打平则输出"Either is winner!".如果x,y属于同一个势力不会打架,当然也不用输出.
3)  tempt x y  表示x诱惑y、将y拉入x的势力。
4)  revolt x   表示x反叛,(自立门户)


输入描述:

第一行输入一个T代表有T组数据
接下来第一行有两个整数N,M,代表N个孩子,M次操作。
接下来有M行。每行输入有如下三种。
1) query
2) fight x y
3) tempt x y
4) revolt x
1<=T<=10;
1<=N,M<=100000;
1<=x,y<=N;

输出描述:

第一行输出"Case #x:",表示第x组测试数据。
接下来输出查询结果,每个结果占一行。
示例1

输入

1
5 9
query
tempt 1 2
query
fight 4 5
query
fight 2 3
query
revolt 2
query

输出

Case #1:
5
4
Either is winner!
4
2 is winner!
3
4

题解

刚刚接触并查集,一看到这道题就知道并查集可解,但是实在想不出来,找不到处理“诱惑”和“反叛”的方法,经过史兄的不断指点,突然发现自己学算法的思维慢慢的被模板化了,进入了一种被被黄大佬称为“不知变通,套模板的境界”,深感忧伤~~~
本题按照并查集模板,在一个帮派中用某个孩子作为根节点的话,当根节点被“诱惑”或者“反叛”的话,将无法处理这个帮派中的其它子节点的归属问题,因为你把根节点作为了某个帮派的标志!这里我们巧妙的引入了一个num[i]数组,记录了孩子i的帮派号,维护多棵根节点为帮派号,其它子节点也为帮派号的二叉树,每一棵树都是一个帮派集合,对于每个帮派号不只有一个人,对各帮派的的合并即为对帮派号的合并,对孩子x的操作转换为x,num[x],par[x]三者的关系!
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int Max_n=1e5+10;
int t,n,m,ans,cnt;  //ans为帮派数,cnt为额外的帮派号
int num[Max_n],par[Max_n],rank[Max_n]; //num[i]为i所在的帮派,par[x]为帮派x的根节点,rank[x]为帮派x的树高
int find(int x)     //帮派的路径压缩
{
    if(par[x]==x)return x;
    else return par[x]=find(par[x]);
}
int main()
{
    scanf("%d",&t);
    int t1=0;
    while(t--){
       
        printf("Case #%d:\n",++t1);
        scanf("%d%d",&n,&m);
        for(int i=1;i<=n;i++){ //初始化
            num[i]=par[i]=i;
            ran[i]=1;
        }
        ans=cnt=n; 
        char s[20];
        int x,y;
        for(int i=1;i<=m;i++){
            scanf("%s",s);
            if(s[0]=='q'){ //query
                printf("%d\n",ans);
            }
            else if(s[0]=='f'){ //fight x y
                scanf("%d%d",&x,&y);
                int fx=find(num[x]),fy=find(num[y]);
                if(fx==fy)continue;
                if(ran[fx]>ran[fy]){
                    par[fy]=fx;
                    ran[fx]+=ran[fy];
                    ans--;
                    printf("%d is winner!\n",x);
                }
                else if(ran[fx]<ran[fy]){
                    par[fx]=fy;
                    ran[fy]+=ran[fx];
                    ans--;
                    printf("%d is winner!\n",y);
                }
                else printf("Either is winner!\n");
            }
            else if(s[0]=='t'){ //tempt x y
                scanf("%d%d",&x,&y);
                int fx=find(num[x]),fy=find(num[y]);
                if(fx==fy)continue;
                if(ran[fy]==1)ans--; //如果帮派内只有自己,被诱惑后总帮派数-1
                ran[fy]--;
                ran[fx]++;
                num[y]=fx; //y归入fx帮派
            }
            else { //revolt x
                scanf("%d",&x);
                int fx=find(num[x]);
                if(ran[fx]==1)continue;
                ran[fx]--;ans++;
                //num[x]=x;   ✘ 由于诱惑操作的存在,每个帮派号可能不止一个人,当帮派里的这个人反叛后,
                //              不另开帮派好的话,此人再被诱惑,路径压缩此人所在的帮派号时就可能会出错!
                num[x]=++cnt; //给x另开一个没有用过的帮派号
                par[cnt]=cnt;ran[cnt]=1;
            }
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值