题解 P3367 【【模板】并查集】

huangzirui

2018-03-19 13:22:57

Solution

update:2020.2.13 ------------ > 并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。 ([摘自百度](https://baike.so.com/doc/6119935-6333082.html)) ------------ 关于并查集和路径压缩: 现在我们假定 ``f[i]`` 表示第 i 个人的老大是谁。 现在我们有甲,乙,丙三个人(分别用 a, b, c 表示) 假设甲和乙打架了,甲做了丙的小弟。则有 ``f[a]=b``, 后来甲打赢了丙 那么丙就是甲的小弟了。有 ``f[c]=a``, 但是如果我们这样表示,丙不能直接知道甲,~~容易自己人打自己人~~ 所以,我们必须直接让丙的大哥变成最大的老大。 定义函数 ``find`` ```cpp int find(int k){ if(f[k]==k)return k; return find(f[k]); }//find 函数可以直接找到最大的老大 f[c]=find(a); //丙的老大是甲 ``` 这时,因为我们要路过他所有的上级,我们也可以顺便使途中经过的人的大哥也变成老大。 ```cpp //路径压缩 int find(int k){ if(f[k]==k)return k; return f[k]=find(f[k]); /* 即: f[k]=find(f[k]); return f[k]; */ } f[c]=find(a); ``` 简直是太巧妙了! 而判定两个人的老大是否相等,只需 ```cpp if(find(a)==find(b)) ``` 就好了。 一些设定: - 一个人不能有两个老大。 - 当已经有老大的人臣服时,老大也将成为胜利的人的小弟。 代码: ```cpp #include<bits/stdc++.h> using namespace std; int i,j,k,n,m,s,ans,f[10010],p1,p2,p3; //f[i]表示i的集合名 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;//初始化i的老大为自己 for(i=1;i<=m;i++){ cin>>p1>>p2>>p3; if(p1==1) f[find(p2)]=find(p3); //p3打赢了p2 else if(find(p2)==find(p3)) //是否是一伙的 printf("Y\n"); else printf("N\n"); } return 0; } ```