带权并查集(hdoj 3038,poj 1182,poj 2492)

阿伟学了三天的带权并查集emmm,基本内容还算是掌握了
带权并查集最重要的还是偏移量的理解,以及偏移量更新的方法,这里主要应用了向量的知识。用例题来说明吧。

hdoj 3038 How Many Answers Are Wrong

题目大意: TT和FF在做一个游戏(boring)。提供一些数的下标 i 和 j ,以即a[i],a[i+1],a[i+2]…,a[j]的和,判断给出的是否是正确答案,如果判断不了就当做正确答案。

题解: 此题要用到带权并查集,也就是每个元素到根节点的距离,即偏移量。用dis表示。初始化为0。偏移量的计算用 向量 比较方便。

当两个点的根节点不相同时,需要将两个节点进行合并,此时设a的根节点为ra,b的根节点为rb,a到b的距离为输入给出的c,因为此时无法判断所得的和是否正确,因此默认为正确答案。进行两个节点的合并,将rb作为ra的根节点,则dis[ra]的计算方法如图所示(向量的应用)
在这里插入图片描述
当两个节点的根节点相同时,这时就可以判断给出的值是否是正确答案了,因为在寻找根节点的时候已经将每个节点都挂在根节点上,形式都如下图所示,因此在计算所给的两点之间的和时,只用dis[a]-dis[b]即可,具体如图(向量)。
在这里插入图片描述
到这里,偏移量就结束了,再看看如何寻找根节点。

在之前,我的查找根节点都是这样写的emmm

int find_root(int x)
{
    while(parent[x]!=x)
        x=parent[x];
    return x;
}

这么写只是从上到下查找根节点,并不会路径压缩,即就算6->7->8,查找到6的根节点是8,并不会把6直接挂到8上面,即不会进行路径压缩。
然后看了dalao的代码

int find_root(int x)
{
    return parent[x]==x ? x : parent[x]=find_root(parent[x]);
}

WTF!!!(对8起,我爆粗口了)
这段代码也就是这样的:

int find_root(int x)
{
    if(parent[x]==x)
        return x;
    else
        return parent[x]=find_root(parent[x]);
}

在查找根节点的时候已经将它挂在根节点上了,也就是如图的亚子~
在这里插入图片描述
Soga~~
从图中也能看出来,在每次递归的时候都需要更新dis的值,即加上其父节点的dis值。。
因此find_root函数就可以这么表示:

int find_root(int x)
{
    if(parent[x]!=x)
    {
        int tmp=parent[x];
        parent[x]=find_root(parent[x]);//递归查找根节点
        dis[x]+=dis[tmp];//更新dis
    }
    return parent[x];
}

最后贴上AC代码:

#include <iostream>
using namespace std;
int n,m;
int parent[200010],dis[200010];
void init()
{
    for(int i=0;i<=n;i++)
    {
        parent[i]=i;
        dis[i]=0;
    }
}
int find_root(int x)
{
    if(parent[x]!=x)
    {
        int tmp=parent[x];
        parent[x]=find_root(parent[x]);//递归查找根节点
        dis[x]+=dis[tmp];//更新dis
    }
    return parent[x];
}
int main()
{
    while(cin>>n>>m)
    {
        init();
        int ans=0;
        for(int i=0;i<m;i++)
        {
            int a,b,c;
            cin>>a>>b>>c;
            a--;
            int aa=find_root(a);
            int bb=find_root(b);
            //判断两点的根节点是否相同,如果相同则
            //说明两点都与根节点有关,可以直接判断是否是错误答案
            //如果根节点不相同,则说明无法从已知条件判断是否是错误答案
            //也就是没必要判断,将两点合并即可
            if(aa==bb)
            {
                if(dis[a]-dis[b]!=c)
                    ans++;
            }
            else
            {
                parent[aa]=bb;
                dis[aa]=-dis[a]+c+dis[b];//向量计算
            }
        }
        cout<<ans<<endl;
    }
}

poj 1182 食物链
题目大意: 动物王国有一种食物链(反正我觉得很神奇),A吃B,B吃C,C吃A(环形),要求你判断给出的关系是否正确,如果判断不了,则默认是正确的。
题解: 此题是并查集必做的题,和上一题差不多,重点也是在偏移量这里。参见dalao博客
当第二三中情况都排除的时候,来判断第一种情况。

首先将偏移量设为三种情况:
dis=0 表示x和父节点是同类
dis=1 表示x被父节点吃
dis=2 表示x吃父节点

在寻找根节点时,因为在找到之后就会将其直接挂到根节点上,因此dis[x]也要更新(在回溯时)。假设x,y的关系为r1,y,z的关系为r2,则x,z的关系为(r1+r2)%3 (具体原因请参见大佬博客)

当x和y的父节点相同时:(此时可以判断给出的关系是否正确)
若d=1,即x和y是同类,只要判断dis[x]和dis[y]是否相等即可。
若d=2,即x吃y,在所有表示“吃”的关系中,都遵循着一个规律:(dis[x]+1)%3=dis[y] ,若x,y满足这个关系,则说明是正确的。

当x和y的父节点不相同时:(此时无法判断,直接合并)
将x的根节点作为y的根节点的根节点
若d=1,即x和y是同类,则y对x的关系为0
若d=2,即x吃y,则y对x的关系为1
综上:无论d是1还是2,y对x的关系都为d-1
则dis[ry]的求法就如下图所示了(向量)
在这里插入图片描述
这个ry->y是3-dis[y],得到的关系正好是与y->ry相反
贴上AC代码:

#include <iostream>
#include <stdio.h>
using namespace std;
int n,k;
int parent[50001],dis[50001];
//dis=0 x与父节点同类
//dis=1 x被父节点吃
//dis=2 x吃父节点
void init()
{
    for(int i=1;i<50001;i++)
    {
        parent[i]=i;
        dis[i]=0;
    }
}
int find_root(int x)
{
    if(parent[x]!=x)
    {
        int tmp=parent[x];
        parent[x]=find_root(parent[x]);
        dis[x]=(dis[x]+dis[tmp])%3;
    }
    return parent[x];
}
int main()
{
    cin>>n>>k;
    init();
    int ans=0;
    while(k--)
    {
        int x,y,d;
//        cin>>d>>x>>y;
        scanf("%d %d %d",&d,&x,&y);//这里必须用scanf,用cin WA了好几发
        if(x>n||y>n)//第二个判别条件
        {
            ans++;
            continue;
        }
        if(d==2&&x==y)//第三个判别条件
        {
            ans++;
            continue;
        }
        int xx=find_root(x);
        int yy=find_root(y);
        if(xx==yy)//如果根节点相同则进行判断
        {
            if(d==1)
            {
                if(dis[x]!=dis[y])
                    ans++;
            }
            else
            {
                if((dis[x]+1)%3!=dis[y])
                    ans++;
            }
        }
        else//如果根节点不同则合并
        {
            parent[yy]=xx;
            dis[yy]=(3-dis[y]+d-1+dis[x])%3;
        }
    }
    cout<<ans<<endl;
}

poj 2492 A Bug’s Life
题目大意: 一种生物有雌雄之分,并且正确的交配方法是雌雄交配,一位教授想判断他们交配方法是否正确(判断是否是同性恋。。)
题解: 此题要用到带权并查集(关于种类并查集和带权并查集的区别,请点击这里
首次,设一个dis数组表示与根节点的关系。当值为0时,表示同性,当值为1时,表示异性。首先初始化为0。
寻找父节点时,更新dis值:
在这里插入图片描述
找到规律:dis[x]=(dis[x]+dis[rx])%2

当两个数据的根节点相同时,只要判断他们的dis值是否相同就行,如果dis值相同说明他们和根节点的关系相同,则他们是同性。反之相反。

当两个数据的根节点不同时,需要进行合并,合并时计算dis[rb]的值时,可以用向量,也可以找规律。如图所示:
在这里插入图片描述
由于向量方向相反不会影响向量的值(两个bug的关系无论从哪个bug看都是一样的),则得到:
dis[rb]=(dis[a]+dis[b]+1)%2
AC代码:

#include <iostream>
#include <cstdio>
using namespace std;
int n,m,flag=0;
int parent[2001],dis[2001];
void init()
{
    for(int i=0;i<2001;i++)
    {
        parent[i]=i;
        dis[i]=0;
    }
}
int find_root(int x)
{
    if(parent[x]!=x)
    {
        int tmp=parent[x];
        parent[x]=find_root(parent[x]);
        dis[x]=(dis[x]+dis[tmp])%2;
    }
    return parent[x];
}
int main()
{
    int k;
    cin>>k;
    for(int i=0;i<k;i++)
    {
        scanf("%d %d",&n,&m);
        flag=0;
        init();
        while(m--)
        {
            int a,b;
            scanf("%d %d",&a,&b);
            int aa=find_root(a);
            int bb=find_root(b);
            if(aa==bb)
            {
                if(dis[a]==dis[b])
                    flag=1;
            }
            else
            {
                dis[bb]=(dis[a]+dis[b]+1)%2;
                parent[bb]=aa;
            }
        }
        cout<<"Scenario #"<<i+1<<":"<<endl;
        if(flag)
            cout<<"Suspicious bugs found!"<<endl;
        else
            cout<<"No suspicious bugs found!"<<endl;
        cout<<endl;
    }
}
发布了32 篇原创文章 · 获赞 12 · 访问量 1388

猜你喜欢

转载自blog.csdn.net/qq_18873031/article/details/99443060