概念:
参考自白皮p85例题
模板:
(1) 普通的带权并查集
int r[max] //权值
int find(int x)
{
if(fa[x]==x)
return x;
else
{
int t=find(fa[x]);
r[x]+=r[fa[x]];
return fa[x]=t;
}
}
(2) 向量做法
以下面两个题目为例,参考自两位大佬(写的很好,建议看下)
(1)题题解
(2)题题解
带权并查集是两个值之间的关系加上了权值,将a与b之间的权值c放在b或a节点上表示a->b或b->a之间的关系为c;
我们在使用并查集find()过程中会出现叶结点向根节点的合并(路径压缩)操作:、
由 x->fa[x],fa[x]->find(fa[x]) 推出 x->find(fa[x]) == x->fa[x]+fa[x]->find(fa[x])
当输入的x与y各自的根节点rx和ry不同时要进行合并:
由 x->rx,y->ry 推出 ry->rx == ry->y+y->x+x->rx(向量的可加性);
当输入的x与y各自的根节点rx和ry相同时要进行比较:
由 x->rx,y->rx 推出 x->y ?= x->rx+rx->y;
例题:
(1)How Many Answers Are Wrong
题意:
输入为 a,b,s表示从a到b的和为s,求错误语句的数量。
思路(1):
参考自https://blog.csdn.net/dextrad_ihacker/article/details/51016017
因为输入较多,所以考虑使用带权并查集判断输入的是否与前面的矛盾;
我们将从a到b的权值赋给a节点,因为题目给的是闭区间,我们又可能遇到从a到b和从b到c合并为从a到c,因为前开后闭区间存在(a,b]+(b,c]=(a,c]的性质,所以使用前开后闭区间即输入后a–。
权值q[a]表示a到a的根节点的距离。
思路(2):
使用向量的做法,我们将区间a到b的和为c定义为a指向b的偏转量为c。
这样我们就可以使用向量的做法。
思路一
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int n,m,ans;
int fa[200010],q[200010];
void ini()
{
for(int i=0;i<=n;i++)
fa[i]=i;
}
int find(int x)
{
if(fa[x]==x)
return x;
int t=find(fa[x]);
q[x]+=q[fa[x]]; //得到x到根节点的距离
//因为先递归到根节点,所以此时fa[x]已经更新为根节点
return fa[x]=t;
}
int main()
{
int x,y,z;
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(q,0,sizeof(q));
memset(fa,0,sizeof(fa));
ini();
ans=0;
for(int i=0;i<m;i++){
scanf("%d%d%d",&x,&y,&z);
x--;
int a=find(x);
int b=find(y);
if(a!=b)
{
fa[b]=a;
q[b]=q[x]-q[y]+z; //向左更新距离
}
else if(q[y]-q[x]!=z) //两者为同一根时判断距离是否合法
ans++;
}
cout<<ans<<endl;
}
return 0;
}
思路二
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
int sum[210000],fa[210000];
int find(int x)
{
if(fa[x]==x)
return x;
int t=fa[x];
fa[x]=find(t);
sum[x]=sum[x]+sum[t];
return fa[x];
}
int main()
{
int n,m,a,b,c,ans=0;
while(scanf("%d%d",&n,&m)!=EOF)
{
ans=0;
for(int i=0;i<=n;i++){
fa[i]=i;
sum[i]=0;
}
for(int i=0;i<m;i++){
scanf("%d%d%d",&a,&b,&c);
a--;
int ra=find(a),rb=find(b);
if(ra!=rb)
{
fa[ra]=rb;
sum[ra]=-sum[a]+c+sum[b];
}
else
{
if(c!=sum[a]-sum[b])
ans++;
}
}
printf("%d\n",ans);
}
return 0;
}
(2)食物链
题意:
现一共就三种动物,a吃b,b吃c,c吃a。
给定两种共k条信息,第一种表示x和y属于同一物种,第二种表示x捕食y;
给定的动物一定属于a、b、c其中一种。
问给出的k条信息有多少是和前面的信息矛盾。
思路(1):
因为有捕食和被捕食关系,所以并查集应带有权值,另外开辟两个数组表示捕食和被捕食(因为要区分x捕食y和y捕食x,所以使用两个数组)
思路(2):
因为一共就三种关系,所以用0表示x与y为同一物种,1表示x被y捕食,2表示x捕食y;
y.num为y的根节点与y的偏转量
思路一
int fa[60100*3]; //相当于三个数组
int n,k;
void ini()
{
for(int i=0;i<=3*n;i++)
fa[i]=i;
}
int find(int x)
{
if(fa[x]==x) return x;
return fa[x]=find(fa[x]);
}
void unite(int x,int y)
{
int a=find(x),b=find(y);
if(a==b) return;
else
fa[b]=a;
}
bool same(int x,int y)
{
return find(x)==find(y);
}
int main()
{
int ans=0;
cin>>n>>k;
int d,x,y;
memset(fa,0,sizeof(fa));
ini();
for(int i=0;i<k;i++)
{
scanf("%d%d%d",&d,&x,&y);
x--;
y--;
if(x>=n||y>=n||x<0||y<0)
{
ans++;
continue;
}
if(d==1)
{
if(!same(x,y+n)&&!same(x,y+2*n))
{
unite(x,y);
unite(x+n,y+n);
unite(x+2*n,y+2*n);
}
else
ans++;
}
else if(d==2)
{
if(x==y)
{
ans++;
continue;
}
if(!same(x,y)&&!same(x,y+2*n))
{
unite(x,y+n);
unite(x+n,y+2*n);
unite(x+2*n,y);
}
else
ans++;
}
}
cout<<ans<<endl;
return 0;
}
思路二
#include<cstdio>
#include<cmath>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
struct stu
{
int par;
int num;
}x[100000];
int n,k;
int find(int a)
{
int t;
if(a==x[a].par)
return a;
t=x[a].par;
x[a].par=find(t);
x[a].num=(x[a].num+x[t].num)%3;
return x[a].par;
}
int main()
{
cin>>n>>k;
int a,b,c,sum=0;
for(int i=0;i<=n;i++){
x[i].par=i;
x[i].num=0;
}
for(int i=0;i<k;i++){
scanf("%d%d%d",&a,&b,&c);
if(b>n||c>n||(a==2&&b==c))
sum++;
else
{
int ra=find(b),rb=find(c);
if(ra!=rb)
{
x[rb].par=ra;
x[rb].num=(x[b].num+a-1+3-x[c].num)%3;
}
else
{
if(a==1&&x[b].num!=x[c].num)
sum++;
else if(a==2&&(3-x[b].num+x[c].num)%3!=a-1)
sum++;
}
}
}
cout<<sum<<endl;
return 0;
}