HDU 3038(带权并查集)

本文介绍了一种使用带权并查集解决谎言检测问题的方法,通过分析句子间的相对关系,判断谎言数量。文章详细解释了带权并查集的概念、路径压缩过程及其在本题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意:

简单来说,给出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*/
}

### HDU 3342 并查集 解题思路与实现 #### 题目背景介绍 HDU 3342 是一道涉及并查集的数据结构题目。该类问题通常用于处理动态连通性查询,即判断若干元素是否属于同一集合,并支持高效的合并操作。 #### 数据描述 给定一系列的人际关系网络中的朋友关系对 (A, B),表示 A 和 B 是直接的朋友。目标是通过这些已知的关系推断出所有人之间的间接友谊连接情况。具体来说,如果存在一条路径使得两个人可以通过中间人的链条相连,则认为他们是间接朋友。 #### 思路分析 为了高效解决此类问题,可以采用带按秩压缩启发式的加权快速联合-查找算法(Weighted Quick Union with Path Compression)。这种方法不仅能够有效地管理大规模数据集下的分组信息,而且可以在几乎常数时间内完成每次查找和联合操作[^1]。 当遇到一个新的友链 `(a,b)` 时: - 如果 a 和 b 已经在同一棵树下,则无需任何动作; - 否则,执行一次 `union` 操作来把它们所在的两棵不同的树合并成一棵更大的树; 最终目的是统计有多少个独立的“朋友圈”,也就是森林里的树木数量减一即是所需新建桥梁的数量[^4]。 #### 实现细节 以下是 Python 版本的具体实现方式: ```python class DisjointSet: def __init__(self, n): self.parent = list(range(n)) self.rank = [0] * n def find(self, p): if self.parent[p] != p: self.parent[p] = self.find(self.parent[p]) # 路径压缩 return self.parent[p] def union(self, p, q): rootP = self.find(p) rootQ = self.find(q) if rootP == rootQ: return # 按秩合并 if self.rank[rootP] > self.rank[rootQ]: self.parent[rootQ] = rootP elif self.rank[rootP] < self.rank[rootQ]: self.parent[rootP] = rootQ else: self.parent[rootQ] = rootP self.rank[rootP] += 1 def solve(): N, M = map(int, input().split()) dsu = DisjointSet(N+1) # 初始化不相交集 for _ in range(M): u, v = map(int, input().split()) dsu.union(u,v) groups = set() for i in range(1,N+1): groups.add(dsu.find(i)) bridges_needed = len(groups)-1 print(f"Bridges needed to connect all components: {bridges_needed}") solve() ``` 这段代码定义了一个名为 `DisjointSet` 的类来进行并查集的操作,包括初始化、寻找根节点以及联合两个子集的功能。最后,在主函数 `solve()` 中读取输入参数并对每一对好友调用 `dsu.union()` 方法直到遍历完所有的边为止。之后计算不同组件的数量从而得出所需的桥接次数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值