题目描述:
小花梨给出n个点,让k位同学对这n个点任意添加无向边,构成k张图。小花梨想知道对于每个点i,存在多少个点j(包括i本身),使得i和j在这k张图中都是连通的。
输入描述:
第一行输入两个正整数n和k,分别表示点的个数和同学数。
接下来分成k部分进行输入,每部分输入格式相同。
每部分第一行输入一个整数ai,表示第i位同学连边的数目。
接下来ai行,每行两个正整数u, v,表示第i位同学将点u和点v之间进行连接。
可能会存在重边或者自环。
(1 ≤ n ≤ 100000,1 ≤ k ≤ 10,1 ≤ u, v ≤ n, 0 ≤ ai≤ 200000)
输出描述:
输出n行,第i行输出在k张图中都和编号为i的点连通的点的数目(包括i本身)
输入样例:
4 2
3
1 2
1 3
2 3
2
1 2
3 4
输出样例:
2
2
1
1
核心思想:
对于每一张图,两点连通即两点属于同一集合,在并查集中,属于同一集合的两点具有相同的祖先。
两点在k张图中都是连通的,那么,两点在k张图中的k个祖先完全相同。
依次处理k张图,将第i个点的祖先push_back到zu[i]中。处理完k张图后,每个zu[i]容器中都有相应i的k个祖先。
用
来记录相同的zu[i](i和j在k张图中都连通,即zu[i]和zu[j]完全相同)的个数,即要输出的答案。
代码如下:
#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<algorithm>
#include<map>
#include<vector>
using namespace std;
typedef long long ll;
const int N=1e5+20;
vector<int>zu[N];
map<vector<int>,int>mp;
int pre[N],num[N];
int find(int x)//并查集的查操作
{
if(pre[x]==x)
return x;
return pre[x]=find(pre[x]);
}
void merge(int x,int y)//并查集的并操作
{
int fx=find(x);
int fy=find(y);
if(fx!=fy)
{
if(num[fx]>num[fy])
{
pre[fy]=fx;
num[fx]+=num[fy];
}
else
{
pre[fx]=fy;
num[fy]+=num[fx];
}
}
return;
}
int main()
{
int n,k;
scanf("%d%d",&n,&k);
for(int z=0;z<k;z++)//依次处理k张图,正在处理的图记为第z张图
{
for(int i=1;i<=n;i++)//并查集初始化
{
pre[i]=i;
num[i]=1;
}
int m,x,y;
scanf("%d",&m);
for(int i=0;i<m;i++)//m个边
{
scanf("%d%d",&x,&y);
merge(x,y);
}
for(int i=1;i<=n;i++)//将每个点的第z个祖先push_back到zu[i]的vector中
zu[i].push_back(find(i));
}
for(int i=1;i<=n;i++)//用map统计完全相同的zu[i]的个数
mp[zu[i]]++;
for(int i=1;i<=n;i++)//输出完全相同的zu[i]的个数,即和点i在k张图中都全连通的点的数量
printf("%d\n",mp[zu[i]]);
return 0;
}