带权并查集(及部分习题)

概念:

参考自白皮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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值