目录
声明:
本系列博客是《算法竞赛进阶指南》+《算法竞赛入门经典》+《挑战程序设计竞赛》的学习笔记,主要是因为我三本都买了按照《算法竞赛进阶指南》的目录顺序学习,包含书中的少部分重要知识点、例题解题报告及我个人的学习心得和对该算法的补充拓展,仅用于学习交流和复习,无任何商业用途。博客中部分内容来源于书本和网络(我尽量减少书中引用),由我个人整理总结(习题和代码可全都是我自己敲哒)部分内容由我个人编写而成,如果想要有更好的学习体验或者希望学习到更全面的知识,请于京东搜索购买正版图书:《算法竞赛进阶指南》——作者李煜东,强烈安利,好书不火系列,谢谢配合。
下方链接为学习笔记目录链接(中转站)
一、路径压缩与按秩合并
当我们在寻找祖先时,一旦元素多且来,并查集就会退化成单次 O ( n ) O(n) O(n)的算法,为了解决这一问题我们可以在寻找祖先的过程中直接将子节点连在祖先上,这样可以大大降低复杂度,均摊复杂度是 O ( l o g ( n ) ) O(log(n)) O(log(n))的
按秩合并也是常见的优化方法,“秩”的定义很广泛,举个例子,在不路径压缩的情况下,常见的情况是把子树的深度定义为秩
无论如何定义通常情况是把“秩”储存在根节点,合并的过程中把秩小的根节点插到根大的根节点上,这样可以减少操作的次数
特别的,如果把秩定义为集合的大小,那么采用了按秩合并的并查集又称“启发式并查集”
按秩合并的均摊复杂度是 O ( l o g ( n ) ) O(log(n)) O(log(n))的,如果同时采用按秩合并和路径压缩均摊复杂度是 O ( α ( n ) ) , α ( n ) O(α(n)),α(n) O(α(n)),α(n)是反阿克曼函数
∀ n ≤ 2 1 0 19729 , α ( n ) ≤ 5 ∀ n ≤2^{10^{19729}},α(n)≤5 ∀n≤21019729,α(n)≤5可以视为均摊复杂度为 O ( 1 ) O(1) O(1)
不过通常情况下我们只需要采用路径压缩就够了
int fa[N];
void init(){
for(int i = 1;i <= n;++i)
fa[i] = i;
memset(Rank,0, sizeof Rank);
}
int getfa(int x){
//查询
if(fa[x] == x) return x;
return fa[x] = getfa(fa[x]);
}
inline void union( int x, int y){
//合并
int fx = getfa(x) , fy = get(y);
fa[fx] = fy;
return ;
}
inline bool same(int x , int y){
//判读是否在同一结合
return getfa(x) == getfa(y) ;
}
//把深度当作秩的 按秩合并
inline void rank_union( int x, int y)
{
fx = getfa(x) , fy = getfa(y);
if(Rank[fx] < Rank[fy]) fa[fx] = fy;
else {
fa[fy] = fx;
if(Rank[fx] == Rank[fy]) Rank[fx]++;
}
return ;
}
并查集能在一张无向图中维护结点之间的连通性,实际上,并查集擅长动态维护许多具有传递性的关系。
1.AcWing 237. 程序自动分析(NOIP2015)
并查集的模板题,注意该题的数据达到1e9,我们可以离散化。
注意多组数据并查集中的fa数组也要清空!
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>
using namespace std;
const int N = 1000007;
struct node{
int x, y, z;
bool operator<(const node &t)const {
return z > t.z;
}
}a[N];
int fa[N];
int n, m;
int b[N << 2];
int find(int x){
if(x == fa[x])return x;
return fa[x] = find(fa[x]);
}
void merge(int x, int y){
x = find(x), y