带权并查集

1.概念: 在并查集的基础上,对其中的每一个元素赋有某些值。在对并查集进行路径压缩和合并操作时,这些权值具有一定属性,即可将他们与父节点的关系,变化为与所在树的根结点关系,
带权值的并查集只不过是在并查集中加入了一个value[ ]数组
value[ ]可以记录很多种东西,不一定是类似距离这种东西,也可以是相对于根节点的状态

2.例题1 poj1182 食物链

题目:动物王国中有三类动物A,B,C,这三类动物的食物链构成了有趣的环形。A吃B, B吃C,C吃A。 
现有N个动物,以1-N编号。每个动物都是A,B,C中的一种,但是我们并不知道它到底是哪一种。 
有人用两种说法对这N个动物所构成的食物链关系进行描述: 
第一种说法是"1 X Y",表示X和Y是同类。 
第二种说法是"2 X Y",表示X吃Y。 
此人对N个动物,用上述两种说法,一句接一句地说出K句话,这K句话有的是真的,有的是假的。当一句话满足下列三条之一时,这句话就是假话,否则就是真话。 
1) 当前的话与前面的某些真的话冲突,就是假话; 
2) 当前的话中X或Y比N大,就是假话; 
3) 当前的话表示X吃X,就是假话。 

你的任务是根据给定的N(1 <= N <= 50,000)和K句话(0 <= K <= 100,000),输出假话的总数。 

Input:

第一行是两个整数N和K,以一个空格分隔。 
以下K行每行是三个正整数 D,X,Y,两数之间用一个空格隔开,其中D表示说法的种类。 
若D=1,则表示X和Y是同类。 

若D=2,则表示X吃Y。

**********思路:*************

(1)此题一看是带权并查集,带的权是什么呢,就是他们之间的关系。所以这里建立集合是以二者之间是否建立过关系为依据,即如果二者根相同,即在一个关系圈子里,是一定存在吃于被吃或者同类的关系的!若根不同即二者之间没有关系,则不管是D=1还是=2都要拉近一个关系网里!

(2)对于(2)(3)容易判断:若(D==2&&x==y||x>N||y>N)则sum++;(sum为假话数量)

(3)对于(1):

<1>如果D==1:若x于y的根是一个即在一个圈子里,如果他俩的关系标记relation[x]!=relation[y]的话即不是同类,此时sum++;若x的根!=y的根,根据题意,这句话成为条件句,需要将xy所在的关系网络合并,为下一步判断做准备。在更新根数组pre的同时,也要更新所有节点于根的关系relation!

<2>如果D==2:若x于y的根是一个即在一个圈子里,如果他俩的关系标记不满足x吃y的关系,此时sum++;若x的根!=y的根,根据题意,这句话成为条件句,需要将xy所在的关系网络合并,为下一步判断做准备。在更新根数组pre的同时,也要更新所有节点于根的关系relation!

(4)上面思路有了,接下来对于细节给予描述:

<1>我们定义每个节点与自己这个关系网络集合根的关系(relation数组来标记)为:

relation[x]==0:表示x与根节点为同类; relation[x]==1:表示x被他的根节点吃; relation[x]==2表示x吃他的根节点;

我们注意:这里的0 1 2不是随便的,

如果 d == 1则 x是同类 ,那么 对 的关系是 0

如果 d == 2 则 吃了 y,  那么 对 的关系是 1, x 对 的关系是 2.

综上所述 ,无论 d或者是为 2,  y 对 的关系都是 d-1

<2>对于xy是否是同类好判断,那么x吃y怎么通过relation数组判断呢?请看下图:


所以若(relation[x]+1)%3==relation[y]表示x指向y也就是x吃y。

<3>相对关系的判断:如果x对y的关系是a,则y对x的关系就是3-a;

<4>在压缩路径过程中,我们也必须压缩关系,在把所有子节点直接连接到根上面的时候,也直接把relaion[x]更新为直接与根的关系,而不是与原来上级的关系。所以我们这里要找出三人之间的关系规律,即已知父亲(上级)与儿子(当前)关系,找到儿子(当前)与爷爷(最终根)的关系,通过穷举发现:

( 儿子relation + 父亲relation ) % 3 = 儿子对爷爷的relation !

有了这个,我们就可以在压缩路径的同时,压缩关系网!

<5>在我们合并集合的时候,在把两个根节点连接的时候也别忘了更新这两个根节点的关系,才能保证整个关系网里面的关系正确!但是我们在合并的时候是已知x与y,只知道x与x集合根的关系,y与y集合根的关系,怎么通过xy找到两集合根之间都关系呢?答案是通过向量!很神奇吧!看图:


所以:综上所述,代码如下:

#include <iostream>
//#include<bits/stdc++.h>
#include<cstring>
#include<cstdio>
using namespace std;
const int maxn=50000+5;
int pre[maxn];//保存前导
int relation[maxn];//保存当前节点与根节点的关系
int N,K;
int finded(int a)
{
    if(a==pre[a])return a;
    int t=pre[a];
    pre[a]=finded(pre[a]);//先递归压缩路径,把各节点先连接到根上,这样才会最多出现爷爷-父亲-儿子的关系,不然关系层数太多无法应用三代间的递归规律。
    relation[a]=(relation[a]+relation[t])%3;//再回溯更新每一点与根节点的直接关系!
    return pre[a];
}
void join(int a,int b,int d)
{
    int fx=finded(a);
    int fy=finded(b);
    pre[fy]=fx;
    relation[fy]=(3-relation[b]+d-1+relation[a])%3;//矢量相加,更新根之间的关系!

}
int main()
{
    scanf("%d%d",&N,&K);
    memset(relation,0,sizeof(relation));//刚开始每个节点与自己是同类
    int sum=0;//记录假话
    for(int i=1;i<=N;i++)
        pre[i]=i;//每个节点父亲是自己
    while(K--)
    {
        int D,x,y;
        scanf("%d%d%d",&D,&x,&y);
        if((D==2&&x==y)||(x>N||y>N))//简单情况
            sum++;
        else if(finded(x)==finded(y))//在一个关系网里面
        {
            if(D==1&&relation[x]!=relation[y])
                sum++;
            else if(D==2&&(relation[x]+1)%3!=relation[y])
                sum++;
        }
        else//不在一个关系网里面
            join(x,y,D);//要拉入关系网建立集合关系

    }
    cout<<sum<<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_40772692/article/details/80010023