并查集&带权并查集

最开始接触并查集是在学Kruskal算法时,我将其分为init、Find和Union三个部分:

init初始化:

int f[MAX];
int rank[MAX];//秩(树的高度),详见Union,可省略
void init(int n)
{
    for(int i=0;i<n;i++)
    {
        f[i]=i;
        rank[i]=0;//可省
    }
}

Find查找:(此处记录了三种我常用的写法,其中路径压缩是指:每次查找时,将查找路径上的每个节点都直接指向根节点)

// 最简洁版:
int Find(int x)
{
    if(x==f[x])
        return x;
    else
        return Find(f[x]);
}

// 路径压缩递归版:
int Find(int x)
{
    if(x!=f[x])
        f[x]=Find(f[x]);
    else
        return x;
}

// 路径压缩非递归版(高效):
int Find(int x)
{
    int xx=x;
    while(x!=f[x])//找根节点
        x=f[x];
    while(xx!=x)
    {
        int t=f[xx];
        f[xx]=x;
        xx=t;
    }
    return x;
}

Union合并:(记录两种写法,其中按秩合并是指:合并时将具有较小秩的树根指向具有较大秩的树根,换句话说,就是将较矮的树作为子树添加到较高的树中)

// 简洁版:
void Union(int u,int v)
{
    int u0=Find(u),v0=Find(v);
    if(u0!=v0)
        f[v0]=u0;
}

// 按秩合并(高效):
void Union(int u,int v)
{
    int u0=Find(u),v0=Find(v);
    if(u0==v0)
        return;
    if(rank[u0]>rank[v0])
        f[v0]=u0;
    else
    {
        f[u0]=v0;
        if(rank[u0]==rank[v0])
            rank[v0]++;
    }
}

 

接下来是带权并查集(重点),记录下来我的一些理解和做题套路。

首先贴大佬博客Orz:

模板代码及详解:https://blog.csdn.net/Chris_zzj/article/details/52227656

典型例题及代码:https://blog.csdn.net/tribleave/article/details/72878239

我对带权并查集的理解:

       就是并查集多了个val数组,表示i和f[i]之间的关系(可以是距离、状态等),Find函数和Union函数要对应改些内容。

我的做题思路:

       1.画数轴;2.写Find函数;3.写Union函数(我通常直接写在main中)

主要记录两道题:

1.POJ2492  A Bug's Life​​​​(种类并查集):

思路中的2、3步详解如下:

这两部分就应该是最难搞的了,然后贴AC代码:

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;

const int MAX=2005;
int n,m;
int f[MAX];
int val[MAX];//0:与f[i]同类,1:与f[i]不同类

int Find(int x)
{
    if(f[x]==x)
        return x;
    int tmp=f[x];
    f[x]=Find(f[x]);
    val[x]=(val[x]+val[tmp])%2;
    return f[x];
}

int main()
{
    int t;
    scanf("%d",&t);
    for(int tt=1;tt<=t;tt++)
    {
        scanf("%d%d",&n,&m);
        for(int i=0;i<=n;i++)
            f[i]=i,val[i]=0;
        int a,b;
        bool sign=true;
        for(int i=0;i<m;i++)
        {
            scanf("%d%d",&a,&b);
            if(sign)
            {
                int u=Find(a);
                int v=Find(b);
                if(u==v)
                {
                    if(val[a]==val[b])
                        sign=false;
                }
                else
                {
                    f[v]=u;
                    val[v]=!(val[a]^val[b]);
                }
            }
        }
        printf("Scenario #%d:\n",tt);
        if(sign)
            printf("No suspicious bugs found!\n\n");
        else
            printf("Suspicious bugs found!\n\n");
    }
	return 0;
}

还有一道类似的题是POJ1182 食物链,那道题更麻烦,分为3类,但套路也差不多。

 

2.HDU3038 How Many Answers Are Wrong  (区间统计并查集):

这道题同样画数轴,写出Find函数和Union函数,更新val[x]和val[fb]。思路就不具体写了,直接贴AC代码吧:

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

const int MAX=200005;
int n,m;
int a[MAX];
int f[MAX];
int val[MAX];//保存的是f[i]到i的和,可能为负

int Find(int x)
{
    if(f[x]==x)
        return x;
    int tmp=f[x];
    f[x]=Find(tmp);
    val[x]+=val[tmp];//修改val值
    return f[x];
}

int main()
{
    while(~scanf("%d%d",&n,&m))
    {
        for(int i=0;i<=n;i++)
            f[i]=i,val[i]=0;
        int a,b,s;
        int ans=0;
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&a,&b,&s);
            a--;//注意!!对区间[l,r]进行记录时,实际上是对(l-1,r]操作,即l=l-1(即势差是在l-1和r之间)
            int fa=Find(a);
            int fb=Find(b);
            if(fa!=fb)
            {
                f[fb]=fa;
                val[fb]=val[a]-val[b]+s;
              /*s:(a,b]
                val[a]:[fa,a]
                val[b]:[fb,b]
                [fa,a]+(a,b]=[fa,b];
                [fa,b]-[fb,b]=[fa,fb];
                又fa=f[fb],故val[fb]=[f[fb],fb]=val[a]+w-val[b]*/
            }
            else
                if(val[b]-val[a]!=s)
                    ans++;
              /*s:(a,b]
                val[a]:[fa,a]
                val[b]:[fb,b]
                [ff,b]-[ff,a]=[a,b]*/
            //此题画数轴更容易理解
        }
        printf("%d\n",ans);
    }
	return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值