Algorithm Notes

LJXin

记录算法刷题、竞赛思路与编程成长,希望每一篇都讲清楚一点、写扎实一点。

P3367 并查集 模板题

P3367 并查集 模板题

🔗 原题链接


📌 问题描述

题目

现在有 \(n\) 个元素,分为若干个集合。需要支持两种操作:

  1. 合并操作 (\(z=1\)):将 \(x\)\(y\) 所在的集合合并为一个集合
  2. 查询操作 (\(z=2\)):查询 \(x\)\(y\) 是否在同一个集合中

输入格式

  • 第一行:\(n, m\)\(n\) 个元素,\(m\) 次操作)
  • 接下来 \(m\) 行:每行三个整数 \(z, x, y\)
    • \(z=1\):合并 \(x, y\) 所在集合
    • \(z=2\):查询 \(x, y\) 是否同属一个集合

输出格式

  • 对每个查询操作(\(z=2\)),输出 YN

约束条件

  • \(1 \leq n \leq 10^5\)
  • \(1 \leq m \leq 10^5\)
  • \(1 \leq x, y \leq n\)

💡 核心思路

每个点都是一颗树的节点,
相同集合的点的根相同,
指向同一个数

并查集 = 树结构 + 找根(find) + 合并(union) + 路径压缩优化


📋 模板步骤

初始化树

   rep(i,1,n+1) fa[i]=i;//每个节点的fa都是自己

    每个循环输入x,y
    先找到他们各自的根 

    x=find(x),y=find(y);
    如果合并,把y的根节点指向x的根节点,
    x=fa[y];
    否则判断两者的根是否相同
    
    找根节点find函数实现
##  优化点
    使用递归可将沿路的的节点的父节点都指向根节点,
    使树的深度接近常数级,提升后续查询效率

    int find(int x){
        if(fa[x]==x) return x;
        return fa[x]=find(fa[x]);
    }

代码实现

完整代码

    const int N=2e5+5;
    int a[N];
    
    int find(int x){
        if(x==fa[x]) return x;
        return fa[x]=find(fa[x]);
    }
    void solve(){
        int n,m;
        cin>>n>>m;
        rep(i,1,n+1) fa[i]=i;
    
        int x,y,z;
        while(m--){
            cin>>z;
            x=find(x);
            y=find(y);
            if(z==1){
                x=fa[y];
            }
            else {
                if(x==y) cout<<"Y\n";
                else cout<<"N\n";
            }
        }
}

📊 复杂度分析

指标 说明
总时间 \(O(m \alpha(n))\) m次操作
空间复杂度 \(O(n)\) fa数组

复杂度说明

  • 没有路径压缩时:find 时间复杂度为 \(O(n)\),总体为 \(O(mn)\),容易超时
  • 使用路径压缩后:单次操作时间复杂度降低到 \(O(\alpha(n)) \approx O(1)\)
  • 反阿克曼函数 \(\alpha(n)\) 增长极慢,\(\alpha(10^5) = 4\),实际上几乎是常数

🎯 总结

  1. 结构本质:并查集用树的结构表示集合关系,根节点唯一标识一个集合
  2. 路径压缩:使用递归替换实现,大幅优化查询效率
posted @ 2026-03-30 18:30  kliu_365  阅读(29)  评论(0)    收藏  举报
TOP