并查集(全解)

首先说并查集有三种

1、第一种是最简单的,没有权值

2、第二种是带权值的并查集

3、第三种是种类并查集(后面以食物链这道题来讲解)

每一种都是有模板的,要尽可能理解后才能长时间记忆

一、(first of all)

所有并查集由三部分组成-------主函数、寻找根节点函数(finds)、合并根节点函数(join)、以上三种只是在这三各部分中有所不同

finds函数

int finds(int x)

{
    if(x!=v[x])
    {
        int y=finds(v[x]);
  
        v[x]=y;//有这一行的目的是剪断一条长链的现状,相当于让他们各自都直接指向他们最后的根节点,而不是跟着题意一个一个的连接起来(长链形状)
        return y;
    }
    return
x;
}

join函数:

void join(int x,int y,int z)
{
    int fx=finds(x);
    int fy=finds(y);
    if(fx!=fy)
        v[fx]=fy;//如果他们的根节点不一样,就要把他们连接起来(是连接他们的根节点而不是连接他们自己)

                    //根节点相连接,就可以保证之前只想原根节点的都跟着更新了新的根节点
}

二、例题:http://acm.hdu.edu.cn/showproblem.php?pid=3038

finds函数

int finds(int x)
{
    if(x!=v[x])
    {
        int y=finds(v[x]);
        w[x]=(w[x]+w[v[x]]);//假如它1-->2的距离是3,2-->3的距离是3,那么1-->3的距离是不是等于3+3,这一行就是这个意思,此节点的权值

                                      //加上根节点的权值,就是此节点到根结点的权值
        v[x]=y;
        return y;
    }
    return x;
}

join函数:

这一点就要涉及权值问题,在权值问题上面三角形是用来判断这一句话正确还是错误,四边形是用来判断在合并根节点时根节点之间的权值问题

1、三角形是当要合并的两个点的根节点一样的时候用来判断正确与否(根节点一样,就不需要合并,要检查之前的操作是否与现在的冲突)

此时的他们满足这个情况

我们就是要判断这个现在题中给出的x到y之间的权值,与之前给出的x到其根节点与y到其根节点而推出来的x到y之间的距离比较,不相等的话,那么这句话就错了。。。

比如2--->1之间的距离是4,3--->1之间的距离是4,那么2--->3之间的距离不就是0,(这里举的是事物对象,不要和数轴联系)

四角形就是用来为连接后的根节点赋值

此时,你已经知道x,y到其根节点之间的距离,题中一旦要让x与y合并,此时三条向量边,你就知道了三条,那不就可以求出来w[x]的值

此时你就用向量加减,如果方向相反就减去它

注意:你不能让fy指向fx,有的题目是不允许的,就比如给你区间【1,2】,和区间【3,4】的长度1,1,接着题目中再给出2到3的距离1,

按照fy指向fx则有:w[fy]=w[x]-z-w[y]就是一个负值,所以尽量都用fx---->fy

void join(int x,int y,int z)
{
    int fx=finds(x);
    int fy=finds(y);
    if(fx==fy)
    {
        if(w[x]!=z+w[y]) ++k;
    }
    else
    {
        v[fx]=fy;
        w[fx]=z-w[x]+w[y];
    }
}

最后这道题要注意一点,你往join函数里面传值的时候,要将传之前y对应的那个变量值加一,要不然他们之间不会连起来

 三、例题:食物链  http://poj.org/problem?id=1182

finds函数:

此时假设

0:同类

1:父吃子

2:子吃父

根据那个四边形w[fx]=3-w[x]+z+w[y]       //3-w[x]的原因是w[x]箭头向上,要反向就要减去他,但是害怕减去之后总和为负值,所以就用3减去它

因为我们用的是fx--->fy,所以此时x指向的y也是父类,当输入的z为2的时候意思是x吃y,x吃y是子吃父,所以此时这个z不变

当输入z为1的时候,是同类的意思,而我们规定同类是0,所以我们要让z减去1

int finds(int x)
{ if(x!=v[x])
    {
        int y=finds(v[x]);
        w[x]=(w[x]+w[v[x]])%3;  //取余于三保证它们之间的关系始终是0、1、2
        v[x]=y;
        return y;
    }
    return x;
}

join函数:

void join(int x,int y,int z)
{
    int fx,fy;
    fx=finds(x);
    fy=finds(y);
    if(fx==fy)
    {
        if(z==1 && w[x]!=w[y])
        {
            ans++;
            return;
        }
        if(z==2 && (w[y]+2)%3!=w[x])
        {
            ans++;
            return;
        }
    }
    else
    {
        v[fx]=fy;
        if(z==2)
        w[fx]=(3-w[x]+2+w[y])%3;
        else if(z==1) w[fx]=(3-w[x]+w[y])%3;
    }
}

这个时候我们规定

0:同类

2:父吃子

1:子吃父

所以当z为2的时候减去一就符合,所以不用,将z等于1和z等于2分开来写

void join(int x,int y,int z)
{
    int fx,fy;
    fx=finds(x);
    fy=finds(y);
    if(fx==fy)
    {
        if(z==1 && w[x]!=w[y])
        {
            ans++;
            return;
        }
        if(z==2 && (w[y]+1)%3!=w[x])
        {
            ans++;
            return;
        }
    }
    else
    {
        v[fx]=fy;
        w[fx]=(3-w[x]+z-1+w[y])%3;
    }
}

下面依次是两种方法的代码

1、

#include<stdio.h>
#include<string.h>
int v[50005],w[50005],ans=0;
int finds(int x)
{
    if(x!=v[x])
    {
        int y=finds(v[x]);
        w[x]=(w[x]+w[v[x]])%3;
        v[x]=y;
        return y;
    }
    return x;
}
void join(int x,int y,int z)
{
    int fx,fy;
    fx=finds(x);
    fy=finds(y);
    if(fx==fy)
    {
        if(z==1 && w[x]!=w[y])
        {
            ans++;
            return;
        }
        if(z==2 && (w[y]+2)%3!=w[x])
        {
            ans++;
            return;
        }
    }
    else
    {
        v[fx]=fy;
        if(z==2)
        w[fx]=(3-w[x]+2+w[y])%3;
        else if(z==1) w[fx]=(3-w[x]+w[y])%3;
    }
}
int main()
{
    int a,s,d,f,g;
    scanf("%d%d",&a,&s);
    for(int i=1;i<=a;++i)
    {
        v[i]=i;
    }
    while(s--)
    {
        scanf("%d%d%d",&d,&f,&g);
        if(g<=0 || g>a || f<=0 || f>a)
        {
            ans++;
            continue;
        }
        if(d==2 && f==g)
        {
            ans++;
            continue;
        }
        join(f,g,d);
    }
    printf("%d\n",ans);
    return 0;
}

2、

#include<stdio.h>
#include<string.h>
int v[50005],w[50005],ans=0;
int finds(int x)
{
    if(x!=v[x])
    {
        int y=finds(v[x]);
        w[x]=(w[x]+w[v[x]])%3;
        v[x]=y;
        return y;
    }
    return x;
}
void join(int x,int y,int z)
{
    int fx,fy;
    fx=finds(x);
    fy=finds(y);
    if(fx==fy)
    {
        if(z==1 && w[x]!=w[y])
        {
            ans++;
            return;
        }
        if(z==2 && (w[y]+1)%3!=w[x])
        {
            ans++;
            return;
        }
    }
    else
    {
        v[fx]=fy;
        w[fx]=(3-w[x]+z-1+w[y])%3;
    }
}
int main()
{
    int a,s,d,f,g;
    scanf("%d%d",&a,&s);
    for(int i=1;i<=a;++i)
    {
        v[i]=i;
    }
    while(s--)
    {
        scanf("%d%d%d",&d,&f,&g);
        if(g<=0 || g>a || f<=0 || f>a)
        {
            ans++;
            continue;
        }
        if(d==2 && f==g)
        {
            ans++;
            continue;
        }
        join(f,g,d);
    }
    printf("%d\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/kongbursi-2292702937/p/10653299.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值