由洛谷的模版来引出话题
跟题干的要求相同,在并查集问题中,n个不同元素被分进若干个集合中,我们要做的通常是查找一个元素所在的集合以及两个元素各自所属集合的合并。
关于该操作的实现方式,可以参考这个例子:
有a,b,c三个人,一个列表f[]表示每个人的老大是谁
假设a和b打架了,a做了b的小弟。则令f[a]=b;
后来a又和c打了架 ,c就是a的小弟了。所以,令f[c]=a;
但是,c不知道b这个老大的老大,这可不合规矩。
所以,我们必须让a的大哥变成最大的老大。
于是我们可以定义函数find来帮助小弟们明确谁才是真正的老大:
int find(int k){
if(f[k]==k) return k; 当自己就是最大的老大时,返回的是本身
return f[k]=find(f[k]);自己是小弟,那就去寻找老大的老大}
同时,为了帮助两个老大判断两个小弟是否同属于自己,可以借助一个表达式:find(k1)==find(k2);即为判断两个元素是否同属一个集合。
更进一步,如若是小弟有了新的老大,那么这自然代表着他的老大已经臣服了,所以为原老大设置新的老大,原先所有的小弟也会臣服于新的老大。i
f[find(小弟)]=find(小弟的新老大(不一定是最大的老大)); 相当于两个集合的合并
模版完整代码如下
#include<bits/stdc++.h> using namespace std; int i,j,k,n,m,s,ans,f[10010],p1,p2,p3; int find(int k){ if(f[k]==k)return k; return f[k]=find(f[k]); } int main() { cin>>n>>m; for(i=1;i<=n;i++) f[i]=i; for(i=1;i<=m;i++){ cin>>p1>>p2>>p3; if(p1==1) f[find(p2)]=find(p3); else if(find(p2)==find(p3)) printf("Y "); else printf("N "); } return 0;
}
还有一道几乎一模一样的题可以练下手:P1551亲戚 传送门
这是对并查集初步的理解,下面再看一道题来熟练一下操作:
P1892 团伙 传送门
题解:
用到的查找元素,合并集合的技巧与模版完全无异。但本题添加了一个合并集合的条件:
我朋友的朋友是我的朋友;
我敌人的敌人也是我的朋友。
那么对于每个强盗来说,都分别有其朋友和敌人组成的集合
因此设定两个数组,分别储存朋友与敌人。对于每行信息,可能会有如下几个处理方式:
1.两个强盗是朋友,将其集合合并
2.一个强盗遇到第一个敌人,将其标记为该强盗敌人集合的根节点
3.一个强盗遇到非第一个敌人,将其与原敌人集合合并
按以上规则对信息进行顺序处理即可
完整代码:
再安利一下坐我旁边的神神神犇反集解法:orz %%%%%%
#include<iostream> #include<cstdio> using namespace std; char p1; int f[1010],enm[1010]; int p2,p3; int find(int k){ if(f[k]!=k) f[k]=find(f[k]); return f[k]; } int main() { int N,M; cin>>N>>M; for(int i=1;i<=N;i++) f[i]=i; for(int i=0;i<M;i++) { cin>>p1>>p2>>p3; if(p1=='F') { f[find(p3)]=find(p2); } if(p1=='E') { if(enm[p2]==0) enm[p2]=find(p3); else f[find(enm[p2])]=find(p3); if(enm[p3]==0) enm[p3]=find(p2); else f[find(enm[p3])]=find(p2); } } int s=0; for(int i=1;i<=N;i++) if(find(i)==i) s++; cout<<s; return 0; }