#Snow{ position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 99999; background: rgba(255,255,240,0.1); pointer-events: none; }

P2024 食物链

P2024 食物链

这道题我用了两种方法,也是刚学(发现并查集学的太少了)

一个是种类并查集,另一个是带权并查集

1种类并查集

题目中有三种关系,分别是吃,被吃和同类;还有三类集合(三种动物),所以就构建出三个集合,且使各个集合之间分别表示这三种关系;

构建三个集合A,B,C;其中A吃B,B吃C,C吃A

对于输入的一对关系,如果是x吃y,则构建吃的关系,即将A中的x和B中的y放入一个集合,B中x和C中y放入一个集合,C中的x和A中的y放入一个集合(因为不知道x和y分别具体属于哪一类,所以都要处理)
而如果是x与y同类,则将A中x,A中y放入同一集合,同理B,C中x和y做同样处理

咳咳,这都是真话才做上边的操作,那怎么判断是不是真话呢?如果输入x吃y,那你就看B中的x和A中的y是不是同一集合(不用看C和B,A和C了,因为修改集合的时候都是一样改的),如果是,那就是之前说过y吃x了,那这句就是假话,你再看A中(不用看B,C了,因为一样)是不是x和y在同一集合,如果是,那就是之前说过x和y是同类,那也说明这句是假话;
ok,下边是代码(我用的是1-n表示A,n+1-2n表示B,2n+1-3*n表示C)
(其实谁吃谁,谁是谁的父亲怎么设没关系)

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;

int n,k,cnt;
int fa[200050];

int find(int x)
{
	if(x==fa[x])return x;
	return fa[x]=find(fa[x]);
}

int main()
{
//	freopen("P2024_2.in","r",stdin);
//	freopen("666.out","w",stdout);
	scanf("%d%d",&n,&k);
	for(int i=1;i<=3*n;i++)fa[i]=i;
	for(int i=1;i<=k;i++)
	{
		int op,x,y;
		scanf("%d%d%d",&op,&x,&y);
		if(x>n||y>n||(x==y&&op==2))
		{
			cnt++;
			continue;
		}
		if(op==1)
		{
			if(find(x)==find(y+n)||find(y)==find(x+n))
			{
				cnt++;
				continue;
			}
			fa[find(x)]=find(y);
			fa[find(x+n)]=find(y+n);
			fa[find(x+2*n)]=find(y+2*n);
		}else
		{
			if(find(x)==find(y)||find(x+n)==find(y))
			{
				cnt++;
				continue;
			}
			fa[find(x)]=find(y+n);
			fa[find(x+n)]=find(y+2*n);
			fa[find(x+2*n)]=find(y);
		}
	}
	cout<<cnt<<endl;
	return 0;
}

2带权并查集

带权并查集其实就是在一个点和父亲的边上加了个权值
不好理解的地方就是合并的时候

我用fa[x]=y,v[x]=0表示是同类;fa[x]=y,v[x]=1表示x吃y;fa[x]=y,v[x]=2表示x被y吃;
(下面用箭头表示指向父亲)
可以手推一下,如果x->y->z->k->t(都是吃的关系)
那v[x]=v[y]=v[z]=v[k]=1,v[t]=0(初值为零);
路径压缩之后就是v[t]=0,v[k]=1,v[z]=2,v[y]=0,v[x]=1;
可以发现就是mod3的过程;
也就是v[x]=(v[x]+v[fa[x]])%3且必须先更新fa[x],所以路径压缩的时候递归改权值即可,如下

int find(int x)
{
	int t=fa[x];
	if(fa[x]!=x)fa[x]=find(fa[x]);
	val[x]=(val[t]+val[x]+3)%3;
	return fa[x];
}

那合并呢?(用Px表示x的根节点)
由于x和Py只有一种关系,儿有两条路径可以找到这关系
一条是x->Px->Py;
另一条是x->y->Py;
推式子让这两条路径求的关系相同
即v[x]+v[Px]=(x和y的关系)+v[y](这里v[x]和v[y]都是到根节点的v值(即路径压缩之后的)),移项就可以求出v[Px],我们要做的是在合并之前将v[Px]给它赋上值,然后合并就好了(有个注意事项,就是你赋值之后下面的合并你就不能再调用find函数了,因为这个时候你调用,那Px的v值已经改了,但它父亲还没改成Py,这就不对了,所以必须提前定义(不信你可以试试,我下面标注了))
还有,为了防止出现移项出现减法之后出现负数的情况,最好是加三再取模
下面是代码

正确的

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;

const int N=5e4+10;
int n,k;
int fa[N],val[N],cnt;

int find(int x)
{
	int t=fa[x];
	if(fa[x]!=x)fa[x]=find(fa[x]);
	val[x]=(val[t]+val[x]+3)%3;
	return fa[x];
}

int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)fa[i]=i,val[i]=0;
	for(int i=1;i<=k;i++)
	{
		int op,x,y;
		scanf("%d%d%d",&op,&x,&y);
		if((x==y&&op==2)||x>n||y>n)
		{
			cnt++;
			continue;
		}
		int fx=find(x);//就是这里,你再看看下面的错误的
		int fy=find(y);
		if(op==1)
		{
			if(fx!=fy)
			{
				val[fx]=(-val[x]+val[y]+3)%3;
				fa[fx]=fy;
			}else if((val[x]-val[y]+3)%3!=0)cnt++;
		}else
		{
			if(fx!=fy)
			{
				val[fx]=((-val[x]+val[y]+1)%3+3)%3;
				fa[fx]=fy;
			}else if(((val[x]-val[y])%3+3)%3!=1)cnt++;
		}
	}
	cout<<cnt<<endl;
	return 0;
}

错误的

#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;

const int N=5e4+10;
int n,k;
int fa[N],val[N],cnt;

int find(int x)
{
	int t=fa[x];
	if(fa[x]!=x)fa[x]=find(fa[x]);
	val[x]=(val[t]+val[x]+3)%3;
	return fa[x];
}

int main()
{
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)fa[i]=i,val[i]=0;
	for(int i=1;i<=k;i++)
	{
		int op,x,y;
		scanf("%d%d%d",&op,&x,&y);
		if((x==y&&op==2)||x>n||y>n)
		{
			cnt++;
			continue;
		}
		if(op==1)
		{
			if(find(x)!=find(y))
			{
				val[find(x)]=(-val[x]+val[y]+3)%3;
				fa[find(x)]=find(y);
			}else if((val[x]-val[y]+3)%3!=0)cnt++,cout<<i<<endl;
		}else
		{
			if(find(x)!=find(y))
			{
				val[find(x)]=((-val[x]+val[y]+1)%3+3)%3;
				fa[find(x)]=find(y);
			}else if(((val[x]-val[y])%3+3)%3!=1)cnt++,cout<<i<<endl;
		}
	}
	cout<<cnt<<endl;
	return 0;
}

谢谢阅读

posted @   Eternal-QX  阅读(19)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示