天是伊格那丢的生日。他邀请了很多朋友。现在该吃晚饭了。伊格那丢想知道他至少需要多少张桌子。你要注意,并不是所有的朋友都认识彼此,所有的朋友都不想和陌
生人呆在一起。
这个问题的一个重要规则是如果我告诉你A认识B, B认识C,这意味着A B C彼此认识,所以它们可以留在一个表中。
例如,如果我告诉你A知道B, B知道C, D知道E,那么A, B, C可以留在一个表中,而D, E必须留在另一个表中。所以伊格那丢至少需要两张桌子。输入以整数T(1<=T<=25)开始,表示测试用例的数量。
然后是T测试用例。
每个测试用例都以两个整数N和M开始(1<=N,M<=1000)。
N表示朋友的数量,从1到N,后面有M行。
每一行由两个整数A和B组成(A!=B),表示朋友A和朋友B认识。
两种情况之间将有一条空行。对于每个测试用例,只输出Ignatius至少需要多少个桌子。不要打印任何空白。
input
2
5 3
1 2
2 3
4 5
father[1~5]:1->2 2->3 3->3 4->5 5->5根节点只有2个
5 1
2 5
output
2
4
错误思路
开个father[N],默认为0,再写个初始化函数,每个案例先变0,再初始化成i,路径压缩,最后在1~n查询有多少个不同的根节点
错因: 没有理解合并并查集的操作
正确思路:合并并查集后再查询
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int N=1001;
int father[N];
void inf(int n)//初始化
{
for(int i=1;i<=N;i++) father[i]=i;
}
bool judge(int x)//判断是否为根节点
{
if(x==father[x]) return true;
else return false;
}
int findFather(int x)
{
//由于x在下面的while中会变成根结点, 因此先把原先的x保存一下
int temp=x;
while(x!=father[x]){//寻找根节点
x=father[x];
}
//到这里, x存放的是根结点。 下面把路径上的所有结点的father都改成根结点
while(temp!=father[temp])
{
int t=temp;//因为temp要被father[temp]覆盖, 所以先保存temp的值, 以修改father[temp]
temp=father[temp];
father[t]=x;//将原先的结点temp的父亲改为根结点x
}
return x; //返回根结点
}
void Union (int a,int b)//U务必大写,因为union是个关键字
{
int faA=findFather(a);
int faB=findFather(b);
if(faA==faB) return;//判断它们是否属于同一集合
else{
father[faB]=faA;//把其中一个的父亲结点指向另一个结点
}
}
int cnt(int n)
{
int ans=0;
for(int i=1;i<=n;i++)
if(judge(i)) ans++;
return ans;
}
int main(){
int T;cin>>T;
while(T--)
{
memset(father,0,sizeof(father));
int n,m;
cin>>n>>m;
inf(n);
while(m--)
{
int a,b;
cin>>a>>b;
Union(a,b);
}
cout<<cnt(n)<<endl;
}
return 0;
}