并查集(一)
并查集
Today is Ignatius' birthday. He invites a lot of friends. Now it's dinner time. Ignatius wants to know how many tables he needs at least. You have to notice that not all the friends know each other, and all the friends do not want to stay with strangers.
One important rule for this problem is that if I tell you A knows B, and B knows C, that means A, B, C know each other, so they can stay in one table.
For example: If I tell you A knows B, B knows C, and D knows E, so A, B, C can stay in one table, and D, E have to stay in the other one. So Ignatius needs 2 tables at least.
Input
The input starts with an integer T(1<=T<=25) which indicate the number of test cases. Then T test cases follow. Each test case starts with two integers N and M(1<=N,M<=1000). N indicates the number of friends, the friends are marked from 1 to N. Then M lines follow. Each line consists of two integers A and B(A!=B), that means friend A and friend B know each other. There will be a blank line between two cases.
Output
For each test case, just output how many tables Ignatius needs at least. Do NOT print any blanks.
Sample Input
2 5 3 1 2 2 3 4 5 5 1 2 5
Sample Output
2 4
解题思路:并查集的入门级别,就是有熟悉的人就会做一张桌子上,然计算一共需要摆多少张桌子。这是我们就会用到并查集将相互认识的人并到一块
初始时每个人都是一个单独的个体,当输入认识的人后,就会出现一个总的认识的人,最后只要判断谁还是和初始值一样,总和就为要摆的桌子的数。
代码:
#include<iostream> using namespace std; const int N=1010; int s[N]; int t; int find_set(int root) { int son,temp; son=root; while(root!=s[root]) root=s[root]; while(son!=root) { temp=s[son]; s[son]=root; son=temp; } return root; } void join(int x,int y) { x=find_set(x); y=find_set(y); if(x!=y) s[x]=s[y]; } int main() { int i,j,n,m,x,y; cin>>t; while(t--) { cin>>n>>m; for(i=1;i<=n;i++) s[i]=i; for(i=0;i<m;i++) { cin>>x>>y; join(x,y); } int ans=0; for(i=1;i<=n;i++) { if(s[i]==i) ans++; } cout<<ans<<endl; } return 0; }
You know that there are n students in your university (0 < n <= 50000). It is infeasible for you to ask every student their religious beliefs. Furthermore, many students are not comfortable expressing their beliefs. One way to avoid these problems is to ask m (0 <= m <= n(n-1)/2) pairs of students and ask them whether they believe in the same religion (e.g. they may know if they both attend the same church). From this data, you may not know what each person believes in, but you can get an idea of the upper bound of how many different religions can be possibly represented on campus. You may assume that each student subscribes to at most one religion.
Input
Output
Sample Input
10 9 1 2 1 3 1 4 1 5 1 6 1 7 1 8 1 9 1 10 10 4 2 3 4 5 4 8 5 8 0 0
Sample Output
Case 1: 1 Case 2: 7
解题思路:宗教信仰,初始每个人都有一个宗教信仰,通过m次询问,有m对同学的宗教信仰相同,求学校里一共有多少宗教信仰。
这也和上面一道题思路差不多。
#include<iostream> #include<cstdio> using namespace std; const int N=50010; int s[N]; int n,m; int find_set(int root) { int son,temp; son=root; while(root!=s[root]) root=s[root]; while(son!=root) { temp=s[son]; s[son]=root; son=temp; } return root; } void join(int x,int y) { x=find_set(x); y=find_set(y); if(x!=y) s[x]=y; } int main() { int i,j,x1,y1,k=1; while(scanf("%d%d",&n,&m)&&n&&m) { for(i=1;i<=n;i++) s[i]=i; for(i=0;i<m;i++) { scanf("%d%d",&x1,&y1); join(x1,y1); } int ans=0; for(i=1;i<=n;i++) { if(s[i]==i) ans++; } cout<<"Case "<<k++<<": "<<ans<<endl; } return 0; }
In the Not-Spreading-Your-Sickness University (NSYSU), there are many student groups. Students in the same group intercommunicate with each other frequently, and a student may join several groups. To prevent the possible transmissions of SARS, the NSYSU collects the member lists of all student groups, and makes the following rule in their standard operation procedure (SOP).
Once a member in a group is a suspect, all members in the group are suspects.
However, they find that it is not easy to identify all the suspects when a student is recognized as a suspect. Your job is to write a program which finds all the suspects.
Input
A case with n = 0 and m = 0 indicates the end of the input, and need not be processed.
Output
Sample Input
100 4 2 1 2 5 10 13 11 12 14 2 0 1 2 99 2 200 2 1 5 5 1 2 3 4 5 1 0 0 0
Sample Output
4 1 1
解题思路:统计和0在一组的成员,以及与0在一组的成员在一组的成员,我们在并查集的基础上加上一个ans数组,来记录同族中的成员个数。
以便如果是一个组的可以相加来统计总的个数,最后输出与0相关联的个数。
代码:
#include<iostream> #include<cstdio> using namespace std; const int N=50010; int s[N]; int n,m; int ans[N]; int find_set(int root) { int son,temp; son=root; while(root!=s[root]) root=s[root]; while(son!=root) { temp=s[son]; s[son]=root; son=temp; } return root; } void join(int x,int y) { x=find_set(x); y=find_set(y); if(x!=y) { s[x]=y; ans[y]+=ans[x]; } } int main() { int i,j,k,tem,tem2; while(scanf("%d%d",&n,&m)) { if(n==0&&m==0) break; for(i=0;i<=n-1;i++) { s[i]=i; ans[i]=1; } while(m--) { scanf("%d",&k); cin>>tem; for(i=0;i<k-1;i++) { cin>>tem2; join(tem,tem2); } } cout<<ans[find_set(0)]<<endl; } return 0; }
Assume N (N <= 10^5) criminals are currently in Tadu City, numbered from 1 to N. And of course, at least one of them belongs to Gang Dragon, and the same for Gang Snake. You will be given M (M <= 10^5) messages in sequence, which are in the following two kinds:
1. D [a] [b]
where [a] and [b] are the numbers of two criminals, and they belong to different gangs.
2. A [a] [b]
where [a] and [b] are the numbers of two criminals. This requires you to decide whether a and b belong to a same gang.
Input
Output
Sample Input
1 5 5 A 1 2 D 1 2 A 1 2 D 2 4 A 1 4
Sample Output
Not sure yet. In different gangs. In the same gang.
解题思路:龙蛇帮,让我们判断他们是同一帮派还是不同帮派,或者不确定。我们加入了一个con数组来存储他的对手(就是跟他不是一个帮派的)
对于输入的两个不同帮派的的数,先判断他们的con是否为0,若是,则直接将该con值==对手;若不为0,则代表他已经有对手,那么现在的对手应该和已有的对手是一个帮派的
所以将二者结合到一起。同理在对另一个对手进行这样的判断。
代码:
#include<iostream> #include<cstdio> using namespace std; const int N=100009; int pre[N],con[N]; int find_set(int root) { int son,temp; son=root; while(root!=pre[root]) root=pre[root]; while(son!=root) { temp=pre[son]; pre[son]=root; son=temp; } return root; } void join(int x,int y) { x=find_set(x); y=find_set(y); if(x!=y) pre[x]=y; } int main() { int n,m,i,j,t,a,b; char c; cin>>t; while(t--) { cin>>n>>m; for(i=1;i<=n;i++) { pre[i]=i; con[i]=0; } while(m--) { getchar(); scanf("%c%d%d",&c,&a,&b); if(c=='D') { if(con[a]==0) con[a]=b; else join(con[a],b); if(con[b]==0) con[b]=a; else join(con[b],a); } else { if(find_set(a)==find_set(b)) cout<<"In the same gang."<<endl; else if(find_set(con[a])==find_set(b)) cout<<"In different gangs."<<endl; else cout<<"Not sure yet."<<endl; } } } return 0; }
In the process of repairing the network, workers can take two kinds of operations at every moment, repairing a computer, or testing if two computers can communicate. Your job is to answer all the testing operations.
Input
1. "O p" (1 <= p <= N), which means repairing computer p.
2. "S p q" (1 <= p, q <= N), which means testing whether computer p and q can communicate.
The input will not exceed 300000 lines.
Output
Sample Input
4 1 0 1 0 2 0 3 0 4 O 1 O 2 O 4 S 1 4 O 3 S 1 4
Sample Output
FAIL SUCCESS
解题思路:我们首先解题时,会想到要把已修复的并且在距离范围内的合并到一起,如果询问时,两者的祖先不一样代表二者不能相连,否则可以。
对于距离,我们先用X,Y数组来存储坐标,再建立dis[][]数组来存储任意两个点之间的距离,并且在设置一个flag数组,代表已修复的网络。每修复一个
网络,就查看该网络能否与其它已修复的网络相连,如果能就合并到一起。
代码:
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> using namespace std; int pre[1010]; int X[1010],Y[1010]; double dis[1010][1010]; bool flag[1010]; int n,d; void init() { for(int i=1;i<=n;i++) { pre[i]=i; flag[i]=false; } } int findx(int x) { if(x==pre[x]) return x; pre[x]=findx(pre[x]); return pre[x]; } void join(int a,int b) { int pa=findx(a); int pb=findx(b); if(pa!=pb) pre[pa]=pb; } int main() { int i,j,x,y; scanf("%d%d",&n,&d); for(i=1;i<=n;i++) { scanf("%d%d",&X[i],&Y[i]); } for(i=1;i<=n;i++) { for(j=i+1;j<=n;j++) { dis[i][j]=dis[j][i]=sqrt(pow(abs(X[i]-X[j]),2)+pow(abs(Y[i]-Y[j]),2)); } } init(); char s[100]; while(scanf("%s",s)!=EOF) { if(s[0]=='O') { int k; scanf("%d",&k); if(flag[k]) continue; flag[k]=true; for(i=1;i<=n;i++) { if(flag[i]&&dis[k][i]<=d) { join(k,i); } } } else if(s[0]=='S') { int k1; int k2; scanf("%d%d",&k1,&k2); if(findx(k1)==findx(k2)) printf("SUCCESS\n"); else printf("FAIL\n"); } } return 0; }