PTA 朋友圈 并查集:按秩归并+路径压缩

7-2 朋友圈                        分数 25                          作者 DS课程组                             单位 浙江大学

某学校有N个学生,形成M个俱乐部。每个俱乐部里的学生有着一定相似的兴趣爱好,形成一个朋友圈。一个学生可以同时属于若干个不同的俱乐部。根据“我的朋友的朋友也是我的朋友”这个推论可以得出,如果A和B是朋友,且B和C是朋友,则A和C也是朋友。请编写程序计算最大朋友圈中有多少人。

输入格式:

输入的第一行包含两个正整数N(≤30000)和M(≤1000),分别代表学校的学生总数和俱乐部的个数。后面的M行每行按以下格式给出1个俱乐部的信息,其中学生从1~N编号:

第i个俱乐部的人数Mi(空格)学生1(空格)学生2 … 学生Mi

输出格式:

输出给出一个整数,表示在最大朋友圈中有多少人。

输入样例:

7 4
3 1 2 3
2 1 4
3 5 6 7
1 6

输出样例:

4

代码长度限制

16 KB

时间限制

400 ms

内存限制

64 MB

整体思路:经典的并查集。题目说有M个俱乐部,那么我们将不同俱乐部中的人都指向第一个人。第一个人记录指向自己的人有多少个。如果别的俱乐部中有某个俱乐部中的人(只要有一个,那么根据朋友的朋友是朋友),就把这两个俱乐部并在一起。

小小的的复习一下并查集的相关知识:

主要的两个小小算法:

1、按秩归并(在Union中)

那么在按秩归并中又有两种不同的方法:

(1)比数高:即让S[root] = -树高(当然一开始所有元素默认初始化为-1,即初始高度为1)

void Union(int S[], int x, int y) {
	int r1 = Find(S, x);
	int r2 = Find(S, y);
	if(S[r2] < S[r1]){//r2的树高比r1高 因为S[r]是树高的相反数
        S[r1] = r2;//r1和r2并在一起,树高就是高的那个r2就变成r1的根结点
    }else{
        if(S[r2] == S[r1]) S[r1]--;//r1和r2的树高相等,那并起来高就多1
        S[r2] = r1;
    }
}

(2)比规模:即让S[root] = -树元素个数(一般采取这种方法,因为可以直观的知道这个集合里的元素有多少个,即为以根结点为下标数组值的相反数)

void Union(int S[], int x, int y) {
	int r1 = Find(S, x);
	int r2 = Find(S, y);
	if (S[r1] < S[r2]) {//当r1中元素更多时
		S[r1] += S[r2];
		S[r2] = r1;
	}
	else{//两种情况:
    //    1.当S[r2] < S[r1],即r2元素更多时,将r1的父结点指向r2。
    //    2.当S[r2] == S[r1],即两个结点都有同一个父结点,也将r1的父结点指向r2。
		S[r2] += S[r1];
		S[r1] = r2;
	}
}

2、路径压缩(在Find中)

int Find(int S[], int x){
    if(S[x] < 0) return x;    //找到集合的根
    else return S[x] = Find(S, S[x]);
}

这里最后一个语句:

else return S[x] = Find(S, S[x]);

做了三件事:1. 找到集合的根   2. 把根变成x的父结点  3. 再返回根。

好了不多bb上代码:

#include<bits/stdc++.h>
using namespace std;
#define N 30005
int S[N];
int Find(int S[], int x) {
	if (S[x] < 0) return x;
	else return S[x] = Find(S, S[x]);
}
void Union(int S[], int x, int y) {
	int r1 = Find(S, x);
	int r2 = Find(S, y);
	if (S[r1] < S[r2]) {
		S[r1] += S[r2];
		S[r2] = r1;
	}
	else if(S[r2] < S[r1] || S[r2] == -1 && S[r1] == -1) {
		S[r2] += S[r1];
		S[r1] = r2;
	}
}
int main() {
	int n, m, t, mx = 0, x, y;
	cin >> n >> m;
	for (int i = 1; i <= n; ++i) S[i] = -1;
	for (int i = 1; i <= m; ++i) {
		cin >> t >> x;
		for (int j = 1; j < t; ++j) {
			cin >> y;
			Union(S, x, y);
		}
	}
	for (int i = 1; i <= n; ++i) {
		if (S[i] < 0) {
			mx = max(mx, -S[i]);
		}
	}
	cout << mx;

	return 0;
}

最后面求最大集合就是用了按秩归并的比规模,以集合的根为数组下标的值就是这个集合元素值的相反数。

如有错误的地方或者不好的地方,请各位大佬指出,本人小萌新,请多多指教!

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值