【新初二暑假第一期并查集阶段性测试】题解

远古测试,我现在才写

T1 葡萄酒

话说题库里没有这道题?

考点:思维+二进制

有一点哈希的想法?

因为我们知道:每一个字符串进行转化后得到的结果总是不同的,自然,如果我们把一个01串看成一个字符串,其转换后所得的数值也是不同的

换言之:每一个二进制所对应的十进制数是唯一的

那么,我们就可以通过对小白鼠进行排列组合,所得到的若干二进制数,一一对应 n n n 个十进制数

考虑需要多少只小白鼠:

对于 1 1 1 瓶酒,我么们不需要小白鼠就知道它一定是有毒的,即需要 0 0 0 只小白鼠

对于 4 4 4 瓶酒,我们有如下的组合: 01 , 10 , 11 01,10,11 01,10,11 ,还有一瓶不喝,就需要 2 2 2 只小白鼠

对于 5 5 5 瓶酒,我们有如下的组合: 001 , 010 , 011 , 100 001,010,011,100 001,010,011,100 ,还有一瓶不喝,就需要 3 3 3 只小白鼠

得出规律:

因为有一瓶酒我们可以不试,所以,实际上只需要试 n − 1 n-1 n1 瓶酒即可,而所需小白鼠的数量恰为 n − 1 n-1 n1 转化为二进制数后的数位

过于简单,没有代码

这你要是写不出来可以回家写作业了

T2 雾山五行

考点:并查集

话说这不是模板吗?

反正用并查集维护就对了(

回去要好好感谢出题人

那么,在此题中,我们的并查集所维护的是该集合的战斗力,每一次合并所合并的也是两个集合的战斗力

最后依次枚举每一个集合,将其战斗力进行一个 max ⁡ \max max 的比较即可

#include<cstdio>
#include<algorithm>
using namespace std;
int fa[5005],Fight[5005];			//Fight 数组存储战斗力
int a[5005];
void make(int num){
	for(int i=1;i<=num;i++){
		fa[i]=i;
	}
}
int find(int num){
	if(fa[num]==num){
		return num;
	}
	return fa[num]=find(fa[num]);
}
void build(int x,int y){
	int xx=find(x),yy=find(y);
	if(xx!=yy){
		Fight[yy]+=Fight[xx];			//战斗力的累加
		fa[xx]=yy;
	}
}			//以上基本都是模板
int main(){
	freopen("war.in","r",stdin);
	freopen("war.out","w",stdout);
	int n,m,x,y,ans=-2147483647;
	scanf("%d%d",&n,&m);
	make(n);
	for(int i=1;i<=n;i++){
		scanf("%d",&Fight[i]);
	}
	for(int i=1;i<=m;i++){
		scanf("%d%d",&x,&y);
		build(x,y);			//合并
	}
	for(int i=1;i<=n;i++){
		ans=max(ans,Fight[find(i)]);			//依次比较
	}
	printf("%d",ans);			//输出
	return 0;
}

T3 龙珠

考点:带权并查集

此题与银河英雄传说高度类似

需要知道A龙珠现在所在的城市,A所在的城市有几颗龙珠,A转移到这个城市移动了多少次

前两问应该是很好求得的,分别用 find 函数和一个处理集合大小的 size 数组即可

难的是第三问

我们先来定义一个 move 数组,表示移动次数

初始化是不难的: m o v e [   i   ] = 0 move[\ i\ ]=0 move[ i ]=0

那么,我们需要什么时候更新 m o v e [   i   ] move[\ i\ ] move[ i ] 呢?

自然,我们可以想到:在合并集合时,我们就可以更新 m o v e [   f i n d ( x )   ] move[\ find(x)\ ] move[ find(x) ] 的值了

void build(int x,int y){
	int xx=find(x),yy=find(y);
	if(xx!=yy){
		deap[yy]+=deap[xx];			//处理集合大小
		move[xx]++;			//使 find(x) 的移动步数自增
		fa[xx]=yy;			//更新父亲
	}
}

但是,我们遇到了与银河英雄传说同样的问题:我们只更新了每一个集合的祖先元素,但是,我们没有更新每一个集合的子元素

怎么办呢?

详细版这边走,简单版下面走

简单来说:在每一次进行 find 操作时,我们不要急着将 f i n d ( f a [   n u m   ] ) find(fa[\ num\ ]) find(fa[ num ]) 的值赋给 f a [   n u m   ] fa[\ num\ ] fa[ num ] ,而是先让 m o v e [   n u m   ] + = m o v e [   f a [   n u m   ]   ] move[\ num\ ]+=move[\ fa[\ num\ ]\ ] move[ num ]+=move[ fa[ num ] ] ,再赋值

很简单,因为 find 是一个递归操作,当处理到 n u m num num 时, f a [   n u m   ] fa[\ num\ ] fa[ num ] 已经处理好了,又因为 n u m num num f a [   n u m   ] fa[\ num\ ] fa[ num ] 处于同一集合内,所以 f a [   n u m   ] fa[\ num\ ] fa[ num ] 移动了多少次, n u m num num 也要移动那么多次,自然就需要在 find 函数里更新 m o v e [   n u m   ] move[\ num\ ] move[ num ]

完整代码:

#include<cstdio>
int fa[10005],deap[10005],move[10005];
void make(int num){
	for(int i=1;i<=num;i++){
		fa[i]=i,deap[i]=1,move[i]=0;
	}			//初始化
}
int find(int num){
	if(fa[num]==num){
		return num;
	}
	int tot=find(fa[num]);
	move[num]+=move[fa[num]];			//上文已提,对 move 数组的更新
	return fa[num]=tot;
}
void build(int x,int y){
	int xx=find(x),yy=find(y);
	if(xx!=yy){
		deap[yy]+=deap[xx];
		move[xx]++;
		fa[xx]=yy;
	}			//上文已提
}
int main(){
	int T;
	scanf("%d",&T);
	for(int TT=1;TT<=T;TT++){
		printf("Case %d:\n",TT);
		int n,m,x,y;
		char ch;
		scanf("%d%d",&n,&m);
		make(n);			//多组输入,每一次都要重新初始化
		for(int i=1;i<=m;i++){
			scanf("\n%c",&ch);
			if(ch=='T'){			//合并操作
				scanf("%d%d",&x,&y);
				build(x,y);
			}else{			//查询操作
				scanf("%d",&x);
				int tot=find(x); 
				printf("%d %d %d\n",tot,deap[tot],move[x]);			//三个问题,依次得解
			}
		}
	}
	return 0;
}

T4 教练

考点:并查集

自然,在输入的时候,我们就可以对每一个团队进行初始化

若某同学没有任何要求,那么他跟其他任何一个人同一个团队都可以

自然,在进行初始化后,可能会出现只有一个人或两个人的团队

这时,我们需要将一个人的团队塞进两个人的团队里面去,如果没有两个人的团队了,就把所有一个人的团队凑到一起

所有团队合并好后,我们需要判断是否有解

假设有解,则人数为 3 3 3 的团队数必然是 n ÷ 3 n\div3 n÷3 (题目保证 n % 3 = 0 n\%3=0 n%3=0

我们只需要统计人数为 3 3 3 的团队数量即可

无解,输出-1

有解,依次输出每一个团队里的成员即可

具体细节参考代码:

#include<cstdio>
int fa[105];
struct node{
	int x[10],cnt;			//x 表示具体成员, cnt 表示成员数
}b[105];
bool flag[105];
void make(int num){
	for(int i=1;i<=num;i++){
		fa[i]=i;
	}
}
int find(int num){
	if(fa[num]==num){
		return num;
	}
	return fa[num]=find(fa[num]);
}
void build(int x,int y){
	int xx=find(x),yy=find(y);
	if(xx!=yy){
		fa[xx]=yy;
	}
}			//并查集模板
int main(){
	freopen("coach.in","r",stdin);
	freopen("coach.out","w",stdout);
	int n,m,x,y;
	scanf("%d%d",&n,&m);
	make(n);
	for(int i=1;i<=m;i++){
		scanf("%d%d",&x,&y);
		flag[x]=flag[y]=1;			//标记,表明 i 和 j 不属于一人团队
		build(x,y);
	}
	for(int i=1;i<=n;i++){
		int tot=find(i);			//找到 i 的祖先
		b[tot].x[b[tot].cnt++]=i;			//相应团队的成员数自增,并将 i 塞入该团队
	}
	for(int i=1;i<=n;i++){
		if(flag[i]==1){			//不属于一人团队
			continue;
		}
		bool tot=0;
		for(int j=1;j<=n;j++){			//优先寻找2人团队
			if(i==j){
				continue;
			}else if(b[j].cnt==2){			//找到了2人团队
				b[j].x[b[j].cnt++]=i;			//新增成员
				b[i].cnt=b[i].x[0]=0;			//原先 i 所在的团队数据清除
				flag[i]=1;
				flag[j]=1;			//进行相应的标记
				tot=1;
				break;
			}
		}
		if(tot){			//已经找到了2人团队
			continue;
		}
		for(int j=1;j<=n;j++){			//找一人团队
			if(i==j){
				continue;
			}else if(b[j].cnt==1){
				b[j].x[b[j].cnt++]=i;
				b[i].cnt=b[i].x[0]=0;
				flag[i]=flag[j]=1;
				break;
			}			//与寻找2人团队类似
		}
	}
	int sum=0;
	for(int i=1;i<=n;i++){
		if(b[i].cnt==3){			//统计三人团队个数
			sum++;
		}
	}
	if(sum!=n/3){			//无解情况
		printf("-1");
		return 0;
	}
	for(int i=1;i<=n;i++){
		if(b[i].cnt==3){			//依次输出每一个三人团队
			printf("%d %d %d\n",b[i].x[0],b[i].x[1],b[i].x[2]);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值