并查集:用以将元素高效分组以及区分。
题目来源:codeforces 1012B
原问题如下,在一个(n*m)的table上,element会做出一种增值行为,如果有三个物质处于某个矩形的三个顶点上,那么在第四个顶点上会自动增值出一个element。现在table上已经存在了一些物质,求出最少仍需多少额外的物质,使得其可以将table覆盖。
对于解这道题来说,灵活的转化思想非常重要。首先我们可以证明,如果一个矩形有两条相邻的边被覆盖满,这个矩形就可以通过增值自行覆盖。进一步我们可以发现,如果将这两条边按垂直方向“打散”之后分散在矩形中,依然可以完成增值。
这个性质的本质是什么呢,可以将存在三个点(r1,c1),(r1,c2),(r2,c1),看作在(r1,c1),(r1,c2),(r2,c1)三对值之间存在联系,因而r2和c2也发生了联系,于是点(r2,c2)也随之存在,因此可以用并查集来解决问题,全集即为(r1,r2……rn,c1,c2……cn)。
每输入一个点,即将对应两个值unite起来;输入完毕后,会出现若干集合。这些集合内部的横纵坐标值自由组合形成的点就是已存在的;而集合之间所能组合形成的点都是尚未涂色的。最终我们希望达到的效果是,所有值都处在一个集合里,这样不论考查哪个点,它的横纵坐标一定是联系起来的。因此我们额外涂点的作用实际上,是将不同集合联系起来,因此结果等于集合数-1
附AC代码
#include<iostream>
#include<cstring>
using namespace std;
int p[400010],r[400010];
int fa(int ch)
{
if(p[ch]==ch)
return ch;
return fa(p[ch]);
}
void unite(int u,int v)
{
int fu=fa(u);
int fv=fa(v);
if(fu!=fv)
{
if(r[fv]==r[fu])
{
r[fu]++;
p[fv]=fu;
}
else if(r[fu]<r[fv])
p[fu]=fv;
else
p[fv]=fu;
}
}
int main()
{
int n,m,q;
cin>>n>>m>>q;
for(int i=1; i<=n+m; i++)
p[i]=i;
for(int i=0; i<q; i++)
{
int r,c;
scanf("%d%d",&r,&c);
unite(r,c+n);
}
int ans=0;
for(int i=1; i<=n+m; i++)
if(p[i]==i)
ans++;
cout<<ans-1<<endl;
return 0;
}