带权并查集——详细解释精华含义及例题hdu 1829&&poj 1703 && poj 1182 && hihocoder 1515 &&hdu 3047 && hdu 3038

非常非常通俗易懂

注意向量的方向:从根向x  root->i=val[i]

hdu 1829 

val[i]=0 表明与结点i与根结点同性 ,val[i]=1是异性  

路径压缩的时候,x会挂到rootx下

路径压缩时:rootx->x=rootx->fa[x]+fa[x]->x 即 val[x]=(val[x]+val[fa[x]])%2;

合并时:y在的集合挂在x在的集合下 rootx->rooty=rootx->x+x->y+y->rooty  其中rootx->x==val[x], x->y==1(因为是异性),

y->rooty==rooty->y==val[y]

判断有同性恋时:val[x]==val[y]

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
//与根结点同性为0,异性为1
int fa[2010];
int val[2010];
int n,m;
int flag=0;
void init(int n)
{
    flag=0;
    for(int i=1;i<=n;++i)
    {
        fa[i]=i;
        val[i]=0;
    }
}
int find(int x)
{
    if(x==fa[x])
        return x;
    else
    {
        int tmp=find(fa[x]);
        val[x]=(val[x]+val[fa[x]])%2;
        fa[x]=tmp;
        return tmp;
    }
}
void merge(int x,int y)
{
    int fx=find(x);
    int fy=find(y);
    if(fx==fy)
    {
        if(val[x]==val[y])       //0+0 ||1+1 是为0 同性
        {
            flag=true;
        }
        return ;
    }
    else                            //将y挂在x下
    {
        fa[fy]=fx;                   // !!!脑子晕乎乎的写成fa[y]=fx;
        val[fy]=(val[x]+1+val[y])%2; // !!!写成val[y]=(val[x]+1+val[y])%2;

    }
}


int main()
{
    int T;
    scanf("%d",&T);
    int kase=0;
    while(T--)
    {
        scanf("%d%d",&n,&m);
        init(n);
        flag=0;
        for(int i=1;i<=m;++i)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            if(flag) continue;
            merge(x,y);
        }
        if(flag)
             printf("Scenario #%d:\nSuspicious bugs found!\n\n",++kase);
        else
             printf("Scenario #%d:\nNo suspicious bugs found!\n\n",++kase);

    }
}

poj 1703与上代码基本一致(罪犯是否在同一个团队相当于是否为同性,假设根结点为龙队,是龙队的为0,不是龙队的为1)

其中还不能判断说明->还没有merge过->find(x)!=find(y)

poj 1182食物链

val[i]=0同类;val[i]=1,i被根结点吃;val[i]=2,i吃根结点

路径压缩时:rootx->x=rootx->fa[x]+fa[x]->x    rootx->fa[x]==val[fa[x]], fa[x]->x==val[x]     val[x]=(val[x]+val[fa[x]])%3;

合并时:y在的集合挂在x在的集合下 rootx->rooty=rootx->x+x->y+y->rooty  其中rootx->x==val[x], x->y==题目给出的同类或x吃y,如果是同类,则x->y值为0(d-1),如果是x吃y,则x->y的值为1(d-1)

y->rooty==3-rooty->y==3-val[y]  [0,1,2] 反过来[3,2,1]在同余的情况下是一样的

判断有撒谎者时:

情况1:数字越界

情况2:同类相食

情况3:d=1 而不同类

d=2而不满足x吃y  x=2时y=0,x=1时y=2(x被根结点吃,y吃根结点,则x吃y),x=0时y=1(x为根结点,y被根结点吃)

(val[x]+1)%3==val[y] 时满足x吃掉y

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=5e4+10;
int fa[N];
int val[N];
int n,m;
//val[i]=0同类,val[i]=1,i被根结点吃;val[i]=2,i吃根结点
int ans=0;
void init(int n)
{
    for(int i=1;i<=n;++i)
    {
        fa[i]=i;
        val[i]=0;
    }
}
int find(int x)
{
    if(x==fa[x])
        return x;
    else
    {
        int tmp=find(fa[x]);
        val[x]=(val[x]+val[fa[x]])%3;
        fa[x]=tmp;
        return tmp;
    }
}
void merge(int x,int y,int d)
{
    int fx=find(x);
    int fy=find(y);
    if(fx==fy)
    {
        if(d==1&&val[x]!=val[y]) ++ans;
        else if(d==2&&(val[x]+1)%3!=val[y]) ++ans;

    }
    if(fx!=fy)
    {
        fa[fy]=fx;
        val[fy]=(val[x]+d-1+3-val[y])%3;
    }
}
int main()
{

        scanf("%d%d",&n,&m);
        init(n);

        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;}                //保证同一条只记录一次
            else if(d==2&&x==y) {++ans; continue;}
            else
                merge(x,y,d);
        }
        printf("%d\n",ans);

    return 0;
}

hihocoder 1515 分数调查

root->i=val[i]代表i比root高多少

路径压缩和上面一样

合并: rootx->rooty=rootx->x+x->y+y->rooty  rootx->x==val[x], x->y==-s(y比x高-s),y->rooty=-val[y]


#include<stdio.h>
const int N=1e5+10;
int fa[N];
int val[N];
int n,m,q;
//val[i]记录比根结点高的分数
int ans=0;
void init(int n)
{
    for(int i=1;i<=n;++i)
    {
        fa[i]=i;
        val[i]=0;
    }
}
int Find(int x)
{
    if(x==fa[x])
        return x;
    else
    {
        int tmp=Find(fa[x]);
        val[x]=(val[x]+val[fa[x]]);
        fa[x]=tmp;
        return tmp;
    }
}
void Merge(int x,int y,int s)
{
    int fx=Find(x);
    int fy=Find(y);
    if(fx!=fy)
    {
        fa[fy]=fx;
        val[fy]=val[x]-s-val[y];
    }
}
int main()
{

        scanf("%d%d%d",&n,&m,&q);
        init(n);

        for(int i=1;i<=m;++i)
        {
            int x,y,s;
            scanf("%d%d%d",&x,&y,&s);
            Merge(x,y,s);
        }
        for(int i=1;i<=q;++i)
        {
            int x,y;
            scanf("%d%d",&x,&y);
            int fx=Find(x);
            int fy=Find(y);
            if(fx==fy)
                printf("%d\n",val[x]-val[y]);
            else
                printf("-1\n");

        }

    return 0;
}

hdu 3047 Zjnu Stadium

题意:B必须坐在A顺时针X位置处

root->i=val[i]   ( i相对于根顺时针偏离的距离 )

合并:rootx->rooty=rootx->x+x->y+y->rooty=val[x]+s-val[y]  (x->y是y比照root->i,是y相对于x顺时针多少,即s)

一开始的时候在压缩路径和合并的时候就求余,反而把自己绕进去了,其实这两步不需要求余

单纯地求出距离根顺时针偏移量做差%300即可

#include<stdio.h>
const int N=5e4+10;
int fa[N];
int val[N];
int n,m;
//val[i]记录偏离根顺时针多少
int ans=0;
void init(int n)
{
    for(int i=1;i<=n;++i)
    {
        fa[i]=i;
        val[i]=0;
    }
}
int Find(int x)
{
    if(x==fa[x])
        return x;
    else
    {
        int tmp=Find(fa[x]);
        val[x]=val[x]+val[fa[x]];
        fa[x]=tmp;
        return tmp;
    }
}
void Merge(int x,int y,int s)
{
    int fx=Find(x);
    int fy=Find(y);
    if(fx!=fy)
    {
        fa[fy]=fx;
        val[fy]=val[x]+s-val[y];
        //printf("%d\n",val[fy]);
    }
    else
    {
//        printf("%d %d\n",val[y],val[x]);
//        printf("%d\n",(300+val[y]-val[x])%300);
        if((val[y]-val[x])%300!=s)
            ++ans;
    }
}
int main()
{
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        init(n);
        ans=0;
        for(int i=1;i<=m;++i)
        {
            int x,y,s;
            scanf("%d%d%d",&x,&y,&s);
            Merge(x,y,s);
        }
        printf("%d\n",ans);
    }

    return 0;
}

hdu 3635 Dragon balls

参考

题意:起初,N个城市每个城市有一个龙珠

T操作代表将A号龙珠在的城市的所有龙珠转移到B龙珠在的城市

Q操作代表询问A号龙珠在的城市,所在城市所有龙珠数目,以及A号龙珠的转移次数

分析:A号龙珠所在城市:find(A),所在城市龙之数目num[i]

重点在龙珠转移次数的修改上,龙珠转移次数=自己转移以及爸爸们的转移,在找爸爸的时候把它一层层递归回来eg,所有操作中,爷爷移动1次,爸爸移动1次,在递归的时候,爸爸移动次数=2,自己移动次数=3(找到一个爸爸就意味着移动了,所以自己肯定有一次)

龙珠数目在合并的时候修改

龙珠转移次数在find函数中修改

#include<cstdio>
using namespace std;
const int N=1e4+10;
int fa[N],num[N],cnt[N];

void init(int n)
{
    for(int i=1;i<=n;++i)
    {
        fa[i]=i;
        num[i]=1;           //城市i有1个龙珠
        cnt[i]=0;           //龙珠i转移过0次
    }

}
int find(int x)
{
    if(x==fa[x])
        return x;
    else
    {
        int tmp=find(fa[x]);
        cnt[x]+=cnt[fa[x]];     //在递归的时候cnt[fa[x]]已经更新好了
        return fa[x]=tmp;
    }


}
void merge(int x,int y)         //城市x的龙珠转移到城市y去
{
    int fx=find(x);
    int fy=find(y);
    if(fx!=fy)                  //题目中说了两个城市是不同的,所以不会出现fx==fy的情况
    {
        fa[fx]=fy;
        num[fy]+=num[fx];
        cnt[fx]++;               //注意不是cnt[x]++;   事实上cnt[fx]=1;  因为每次移动都是所在集合的根去移动,而根在合并的时候一定只移动一次,下一次就换根啦
    }                            //这样在搜cnt[x],在find过程中就能把这个加上去了,假设最终根是fy,fy的转移次数在递归里是不会被加进去的

}
int main()
{
    int T;
    scanf("%d",&T);
    int kase=0;
    while(T--)            //如果a==0,b==0
    {
        ++kase;
        int sum=0;
        int n,q;
        scanf("%d%d",&n,&q);
        init(n);
        char ch;
        int x,y;
        for(int i=1;i<=q;++i)
        {
            scanf("\n%c",&ch);
            if(ch=='T')
            {
                scanf("%d%d",&x,&y);
                merge(x,y);
            }
            else
            {
                ++sum;
                scanf("%d",&x);
                int ans=find(x);
                if(sum==1) printf("Case %d:\n%d %d %d\n",kase,ans,num[ans],cnt[x]);
                else printf("%d %d %d\n",ans,num[ans],cnt[x]);
            }
        }
    }
    return 0;
}

hdu 3038 区间型问题,感觉很容易想到线段树上去。 一开始觉得可能有点难,但只要结合最上面的博客,想清楚val[i]的含义,就很简单了

1、被多组数据wa怕了,以后看到题直接按照多组数据的格式写好了

2、区间型问题结合前缀和思想,root->i=val[i] 代表sum[i]-sum[root]  (其中root始终是小的一端)

按照上面的理解,题目给的是闭区间, 即sum[y]-sum[x-1]  所以更新的时候需要merge(x-1,y)

路径压缩:如果find(x)<find(y) 把y在的合并到x在的 rootx->rooty=rootx->x+x->y+y->rooty=val[x]+s-val[y]

如果find(x)>find(y) 把x在的合并到y在的 rooty->rootx=rooty->y+y->x+x->rootx=val[y]-s-val[x]

判断错误:find(x)==find(y)时 if((val[y]-val[x])!=s) 错误

#include<stdio.h>
const int N=2e5+10;
int fa[N];
int val[N];
int n,m,q;
//root->i=val[i]记录i比根结点的和  令小的为根结点
int ans=0;
void init(int n)
{
    for(int i=0;i<=n;++i)
    {
        fa[i]=i;
        val[i]=0;
    }
}
int Find(int x)
{
    if(x==fa[x])
        return x;
    else
    {
        int tmp=Find(fa[x]);
        val[x]=(val[x]+val[fa[x]]);
        fa[x]=tmp;
        return tmp;
    }
}
void Merge(int x,int y,int s)
{
    int fx=Find(x);
    int fy=Find(y);
    if(fx!=fy)
    {
        if(fx<fy)
        {
            fa[fy]=fx;
            val[fy]=val[x]+s-val[y];
        }
        else
        {
            fa[fx]=fy;
            val[fx]=val[y]-s-val[x];
        }
    }
    else
    {
        if((val[y]-val[x])!=s)
        {
            ++ans;
        }
    }
}
int main()
{

        while(scanf("%d%d",&n,&m)!=EOF)
        {
            init(n);
            ans=0;
            for(int i=1;i<=m;++i)
            {
                int x,y,s;
                scanf("%d%d%d",&x,&y,&s);
                --x;
                Merge(x,y,s);
            }
            printf("%d\n",ans);
        }



    return 0;
}

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值