HDU 3038(带权并查集)

题意:

简单来说,给出n句话,判断谎言的个数,谎言就是与前面真话冲突的话。带权并查集的典型题。

对带权并查集的理解:

个人觉得带权并查集就是给出了结点的相对关系,已知相对关系的点就放到一个连通的块里,也就是并查集有同一个根节点。
这些相对关系又能转化到一个结点上,这个结点就是它们共有根节点,而转化到同一结点的过程就是路径压缩的过程。权值即数组r[ ]里面的值是该结点到其父节点的距离,只是在路径压缩中该结点前面的结点的父节点都变成了根节点。
以本题为例,下图为压缩路径的回溯过程,就是修改到父节点距离为到根节点的距离的过程(当然father数组的值也变为了根节点,r[ ]的意义依然是到父亲节点,只是父亲节点都变为了根节点。)最后一个图 1结点到根节点10的距离是9,手绘失误
路径压缩回溯

思路:

利用前缀和的思想,[r~l]=[r,1]-[l-1,1]的值。我们用节点x代表x的前缀和,那么给出的【l,r】=4之间的和就是r结点比l-1结点大4,就是给出了相对关系,然后再利用矢量的思想进行相对关系转换。
矢量关系,合并时

代码:

#include<cstdio>
#include<string>
#include<cstring>
#include<iostream>
using namespace std;
const int maxn=2e5+10;
int father[maxn],r[maxn];
int ans;
void init(int n)
{
    for(int i=0;i<maxn;i++)
    {
        father[i]=i;
        r[i]=0;
    }
}
//路径压缩
int findfather(int x)
{
    if(x!=father[x])
    {
        int pre=father[x];
        father[x]=findfather(father[x]);
        r[x]=r[x]+r[pre];
    }
    return father[x];//要return father[x]而不是x
}
void combine(int x,int y,int s)
{
    int fx=findfather(x);
    int fy=findfather(y);
    if(fx==fy)
    {
        if(s!=r[x]-r[y]) ans++;
    }
    else
    {
        father[fx]=fy;
        r[fx]=s+r[y]-r[x];//合并转移
    }
}
int main()
{
    int n,m;
    while(~scanf("%d%d",&n,&m))
    {
        init(n);
        ans=0;
        for(int i=1;i<=m;i++)
        {
            int a,b,s;
            scanf("%d%d%d",&a,&b,&s);
            combine(a-1,b,s);
        }
        printf("%d\n",ans);
    }
    return 0;
    /*理解为b的前缀和比a-1的前缀和大s*/
}

猜你喜欢

转载自blog.csdn.net/qq_44607936/article/details/98891448