某个比赛现场有来自不同学校的N名学生,给出M对“两人同属一所学校”的关系, 请推断学校数量,并找出人数最多的学校。
输入格式:
第一行是一个在[2, 1000]范围的整数N,接下来N行,每行是一个在现场的学生的姓名,每个姓名仅由字母组成,长度不超过30。
接下来一行是非负整数M,表示有M对关系;
然后是M行,每行是用空格间隔的两个人名,表示同属一所学校。
输出格式:
在一行内分别输出学校的数量以及人数最多学校的人数,用一个空格分隔。
输入样例:
8
Bill
Ellen
Ann
Chris
Daisy
Flin
Henry
Grace
5
Ann Chris
Ellen Chris
Daisy Flin
Henry Ellen
Grace Flin
输出样例:
3 4
思路:判断同属某个集合肯定是要用并查集。一种较为简单的思路是把名字通过map映射成数字,然后进行并查集操作。但是这里我选择了用map直接写并查集,理解起来稍微有些奇特。
#include <bits/stdc++.h>
using namespace std;
string p[1005];
map<string,string> fa; //map类型的fa数组
map<string,int> per; //存取以某个学生为代表的学校的人数
int n,m;
string find(string x){ //查找根节点并进行路径压缩
if(fa[x]==x){
return x;
}
return fa[x]=find(fa[x]); //路径压缩
}
void merge(string x,string y){ //合并
string xx=find(x),yy=find(y); //查找根
if(xx!=yy){
fa[xx]=yy;
}
}
int main(){
cin>>n;
for(int i=0;i<n;i++){
cin>>p[i];
}
for(int i=0;i<n;i++){ //初始化fa数组 和 per数组
fa[p[i]]=p[i];
per[p[i]]=0;
}
cin>>m;
while(m--){
string a,b;
cin>>a>>b;
merge(a,b); //对输入的进行合并
}
int school=0,maxp=0; //初始化学校数量和最大人数
map<string,string>::iterator it;
for(it=fa.begin();it!=fa.end();it++){
fa[it->first]=find(fa[it->first]);
}/*再次find的原因是输入merge时,每次输入更新的根节点
并不是最终的根节点,所以全部merge完毕需要再次路径压缩
因为需要通过路径压缩这一步统计每个学校人数,这的思路有点暴力了*/
for(it=fa.begin();it!=fa.end();it++){
per[it->second]++; //遍历,每个根代表的学校人数+1
if(it->first==it->second){
school++; //遍历,每个相同则学校数+1
}
}
map<string,int>::iterator itt;
for(itt=per.begin();itt!=per.end();itt++){
maxp=max(maxp,itt->second); //不断更新每个学校人数的最大值
}
cout<<school<<" "<<maxp<<endl; //输出
return 0;
}
有一个小细节是输入后再次find()进行路径压缩,否则最后遍历更新每个学校人数时会出错,因为输入完成后每个元素的fa[x]并不一定是他最终的代表元素,可能是他的前一个元素。为了避免这种情况,最后merge完毕之后必须再次路径压缩。
可能有人会问merge的时候不是已经find一遍了吗,为什么还要再来一次?
因为输入时merge是一次一次更新的,每次find只对现有输入的集合进行合并和压缩,并不是最终情况。