今天学习了以前没有接触过的并查集,差点就酿成0ac惨案,还是总结一下。
一般的并查集就是通过一个树来储存该元素所属集合,一般来讲为了优化都会进行路径压缩(虽然听起来很高大上但其实就是将一棵树变成了只含有一个父节点的很简单的树)(虽然好像有时候不能进行路径压缩需要按秩合并,但是我现在还不会)。
- 一般需要一个数组存储父节点就可以了,种类并查集及带权并查集还需要其他数据结构进行维护(现在还不太会)。
- 初始化:一般将每一个元素的父节点设置为自己
for(int i=1;i<=n;i++)
{
parent[i]=i;
}
- find函数(寻找根节点):
int find(int x)
{
return x==parent[x]?x:parent[x]=find(parent[x]);
}
- 合并函数
void merge(int x,int y)
{
int a=find(x); int b=find(y);
if(a==b) return;
parent[a]=b;
}
- 关于简单并查集的操作
1. 计算总共有多少个集合:
每个集合的根节点的特征是该节点的父节点是本身,因此只需要遍历一遍然后统计父节点是本身的结点的数目,就是集合的数目
2. 计算每个集合有多少元素:
可以用一个数组来储存每一个集合有多少个元素,这样的话在初始化的时候每个集合的元素个数都为1,在合并时需要更新数组,合并函数变为如下:
void merge(int x,int y)
{
int a=find(x); int b=find(y);
if(a==b) return;
parent[a]=b;
number[b]+=number[a]; //number[find(x)]即为结点x所在集合元素个数
}
3. 计算一个树是否形成环:
并查集问题经常配合树、图等出现,不仅要掌握用并查集处理他们,还要善于将问题转换为树、图问题(例如UVA - 1160)
判断这个树是否有环时,将每一条边的两个结点纳入同一个集合,如果某个结点在这条边之前已经处于集合之中(即两节点的父节点相同),说明必定有环存在
4.关于带权并查集
带全并查集使用还是比较广泛的,能解决的问题也比较多,例如比较经典的POJ 1182 食物链问题还有其他比较类似的HDU - 1829 A bug’s life问题。解题思路还有代码在这个博客介绍的已经比较详细了,这里写一下我的心得体会。
并查集问题在解决很多元素之间关系时比较好用,而元素之间的关系往往不是简单的包含还是不包含,而一般是更复杂的同类、吃与被吃的关系,或者是gay还是异性恋的关系等等,但究其本身都可以转换成元素与根节点的关系进行维护,我们通过数学符号用不同的数字等表示不同的关系,并进行类似数学中的向量运算表示关系之间的传递(虽然我也不知道为什么可行,应该是有更深层次的数学原理吧)。
就食物链问题为例,我们用0表示同类,1表示吃,2表示被吃,这样做的好处在于-0还是0,即同类反过来还是同类,-1+3(状态数)=2,即吃反过来变成了被吃,这样就巧妙的将问题转换成数学问题。
刚开始看到询问是不是谎话我就自闭了,我怎么知道是不是谎话,但是仔细分析后,谎话就是与前面关系冲突的描述,只要不冲突就确定关系。类似的对于这种边确定关系边确定关系是否矛盾的问题都可以这样解决。
这里再放一道我一眼就看出来就是带权并查集可是还是没有做出来的简单题
简单描述一下题意就是说两个情侣(撒狗粮)玩游戏,一个说某一区间的和,另一个得判断矛盾不,询问矛盾的个数。
虽然很容易想到是带权并查集,但是如何确定根节点好像没有想的那么容易,维护的数组肯定是从该节点到根节点的的和,可是描述一个区间的几个点完全可能没有交集。那么就要思考如何创造交集(我没有想出来)。实际上我们只要把区间变成左开右闭的(左闭右开也完全可以),这样的话可定会有交集。
附AC码:
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN=200005;
int fa[MAXN];
int rela[MAXN];
int n,m,ans;
void init()
{
for(int i=0;i<=n;i++)
{
fa[i]=i;
rela[i]=0;
}
ans=0;
}
int find(int x)
{
if(x==fa[x]) return x;
int tmp=fa[x];
fa[x]=find(fa[x]);
rela[x]+=rela[tmp];
return fa[x];
}
bool check(int u,int v,int r)
{
if(find(u)==find(v))
{
int relation=rela[u]-rela[v];
return relation==r;
}
else
return true;
}
void merge(int u,int v,int r)
{
int fu=find(u); int fv=find(v);
if(fu==fv)
return;
fa[fu]=fv;
rela[fu]=rela[v]-rela[u]+r;
return;
}
int main()
{
int u,v,r;
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
for(int k=0;k<m;k++)
{
scanf("%d%d%d",&u,&v,&r);
u--;
if(check(u,v,r))
merge(u,v,r);
else
ans++;
}
printf("%d\n",ans);
}
return 0;
}
5. 种类并查集(未完待更)