并查集与加权并查集

洛谷/食物链/并查集

所用代码可能包含部分引用他人,因时间太久记不清是否引用,哪些引用,以及引用何处。侵删。

并查集是一种树形结构,用于对同类元素集合化,路径压缩后一定程度上能减小查找的时间复杂度。

合并查找是并查集的两个基本操作
查找
int find(int x)
{
if(x==father [x]) return x;
else return father[x]=find(father[x]); //路径压缩
}

合并
void unoion(int x,int y)
{
int s1=find(x),s2=find(y);
if(s1!=s2) father[s1]=s2;
}

洛谷/食物链
根据题意,语句为假有三种情况:

  1. x,y编号超过n
  2. 语句1:x与y存在捕食关系
  3. 语句2:x与y是同类,混淆x与y的捕食关系

解法一

以x+n表示x的猎物,以x+2*n表示x的天敌
在这里插入图片描述

当x与y是同类生物时,合并x与y, x+n与y+n, x+2n与y+2n;
当x捕食y时,合并y与x+n, y+n与x+2n,y+2n与x

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
int n,m,ans;
int f[300100];
int find(int x)
{
	if(x==f[x]) return x;
	else return f[x]=find(f[x]);
}
void unoion(int x,int y)
{
	int s1=find(x),s2=find(y);
	if(s1!=s2) f[s1]=s2;	
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=3*n;i++) f[i]=i;
	for(int i=1;i<=m;i++)
	{
	int d,x,y;	
	scanf("%d%d%d",&d,&x,&y);
	if(x>n||y>n)  {ans++;continue;}	
	if(d==1)
	{
		if(find(x)==find(y+n)||find(x)==find(y+2*n)) {ans++;continue;}
	    unoion(x,y);unoion(x+n,y+n);unoion(x+2*n,y+2*n);
	}
	else
	{
		if(x==y) {ans++;continue;}
  	    if(find(x)==find(y)||find(y+n)==find(x)) {ans++;continue;}
	    unoion(y,x+n);unoion(x+2*n,y+n);unoion(x,y+2*n);
	}
	}
	cout<<ans<<endl;
	return 0;
}

解法二

方法一需要使用三倍的空间,当我们考虑在原大小的空间下解决时,可以考虑在原有并查集的基础上增加边的权值,记录节点间的关系。无权并查集用于结合有共同特征的集合,而加权并查集单元节点间的关系需要根据权值得出。
对于这道题,我们对边赋予权值“0,1,2”,并定义“0表示同类,1表示被吃,2表示吃”。
子节点与祖先节点间的关系即路径权值之和对3取模。

在这里插入图片描述

E–>A (2+1+2)%3=2 E捕食A
E–>B (2+1)%3=0 E和B同类
D–>A (0+2)%3=2 D捕食A

#include<bits/stdc++.h>
using namespace std;
int n,k,ans=0;
struct animal
{
    int father,relation;
}a[60000]={};
/*以上是变量说明阶段,a结点用来存所有的动物,
father代表他的父节点是哪个,relation用来代表他和他的父节点的关系
0表示同类,1表示被吃,2表示吃;*/
int getfather(int k)
{
    if(a[k].father!=k)//如果k的父节点不是自己 
    {
        int temp=a[k].father;
        //先用temp来暂存下k的直接父亲 
        a[k].father=getfather(a[k].father);
        //k的父亲追溯到祖先那里去 
        a[k].relation=(a[k].relation+a[temp].relation)%3;
        //k跟祖先的关系,就是k跟祖先之间所有的边权值之和%3 
    }
    return a[k].father;//返回 
}
//求根结点和压缩路径。 
void read()
{
    scanf("%d%d",&n,&k);

    for(int i=1;i<=n;i++)
    {
        a[i].father=i;
        a[i].relation=0;
    }
}
//初始化和读入,父节点默认是自己,关系默认是同类 

void work()
{
    for(int i=1;i<=k;i++)
    {
        int pattern,x,y;
        scanf("%d%d%d",&pattern,&x,&y);
        //pattern,关系类型 
        if(x>n||y>n)
        {
            ans++;
            continue;
        }//题目要求 
        if(pattern==1)//如果是同类的关系的话 
        {
            if(getfather(x)==getfather(y))
            {
                if(a[x].relation!=a[y].relation)
                ans++;
                /*如果在同一个集合里面,
                 并且,他们跟祖先的关系不一样
                 那就是假话
                 (如果是同类,跟祖先的关系应该是一样的)*/ 
            }
            else//如果不在同一个集合,就不存在矛盾 
            {
                a[a[x].father].relation=(a[y].relation-a[x].relation+3)%3;
                //这里加三是为了防止负数的出现 
                a[a[x].father].father=a[y].father;
                /*这里是把x的父亲作为了y的父亲的儿子结点,这样我们就完成了并的操作。x的父亲和y的父亲的关系为什么是这样呢?。既然x和y是同类,那么他们合并后和祖先的关系应该是一样的。也就是说x与y的父亲的关系和y与y的父亲的关系是一样的。也就是说x到y的父亲的权值和,与y到y的父亲的权值和是一样的*/ 
            }
        }
        if(pattern==2)//如果是吃的关系 
        {
            if(x==y)
            {
                ans++;
                continue;
            }//题目要求 
            if(getfather(x)==getfather(y))
            {
                if(a[x].relation!=(a[y].relation+1)%3)
                //这里+1代表被吃的关系。和同类稍有不同 
                ans++;
            }
            else//同上一种情况 
            {
                a[a[x].father].relation=(a[y].relation-a[x].relation+4)%3;
                //这里和上面略有不同,是因为吃的关系,权值不是0;需要+1; 
                a[a[x].father].father=a[y].father;
            }
        }
    }
}
int main()
{
    read();
    work();
    printf("%d",ans);
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值