边三连通分量学习笔记
边三可以是一个结构也可以是一种 trick,所以会被塞到 trick 合集 里面。
在一张无向图上,如果两个点之间存在
求边三连通分量总体考虑把不同连通分量两两区分开,如已知点集
先求出一棵生成树,给每个非树边赋一个随机权值,并一同添加到这条非树边包含的路径上的边。考虑断开的边的情况:
- 一条树边:树边权值为 0
- 一条树边和一条非树边:树边权值恰好为任意一条非树边权值
- 两条树边:可以发现是两条树边的权值相同时,同时断开会导致中间的一部分分裂出去。
事实上,可以把上述结论总结为
写的时候写两个 xor hashing 用来记录点权和边权即可。
边三本身的结构很特殊,首先边三是边双的子结构,每个边双内部的边三缩起来之后会形成严格(每条边都在恰好一个环内)的仙人掌。
在处理切两条边图不连通的计数时,除了简单地通过边哈希值判定(哈希值相同即切割后不连通),可以发现切割仙人掌同一个环上的两条边会使图不连通,这提示边的哈希值对应了在仙人掌上的环的位置。
#include <bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
using namespace std;
using namespace __gnu_pbds;
using ull = unsigned long long;
namespace Rand{
ull cur = 19260817;
ull getr(){
cur ^= cur << 15;
cur ^= cur >> 17;
cur ^= cur << 7;
return cur;
}
}
const int N = 5e5 + 5;
int n, m;
vector<pair<int, int>> e[N];
vector<int> e2[N];
gp_hash_table<ull, bool> sedge;
gp_hash_table<ull, vector<int>> mp, mp2;
vector<vector<int>> ans;
ull mark[N], tag[N];
int dfsnum, dfn[N], rev[N];
void dfs(int u, int fae){
rev[dfn[u] = ++dfsnum] = u;
for (auto [v, eid] : e[u]){
if (eid == fae) continue;
if (!dfn[v]){
e2[u].push_back(v);
dfs(v, eid);
mark[u] ^= mark[v];
}else if (dfn[u] >= dfn[v]){
ull eh = Rand::getr();
mark[u] ^= eh;
mark[v] ^= eh;
sedge[eh] = 1;
}
}
}
void dfs2(int u){
for (int v : e2[u]){
tag[v] ^= tag[u];
dfs2(v);
}
}
int main(){
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
// freopen("in.txt", "r", stdin);
// freopen("out.txt", "w", stdout);
cin >> n >> m;
for (int i = 1; i <= m; i++){
int u, v;
cin >> u >> v;
e[u].emplace_back(v, i);
e[v].emplace_back(u, i);
}
sedge[0] = 1;
vector<int> rt;
for (int i = 1; i <= n; i++)
if (!dfn[i]){
rt.push_back(i);
dfs(i, 0);
}
for (int i = 1; i <= n; i++){
int j = rev[i];
if (sedge.find(mark[j]) != sedge.end()) tag[j] ^= Rand::getr();
mp[mark[j]].push_back(i);
}
for (auto [_, vec] : mp)
if ((int)vec.size() > 1)
for (int i = 1; i < (int)vec.size(); i++){
ull eh = Rand::getr();
tag[rev[vec[i - 1]]] ^= eh;
tag[rev[vec[i]]] ^= eh;
}
for (int u : rt) dfs2(u);
for (int i = 1; i <= n; i++) mp2[tag[i]].push_back(i);
for (auto [_, vec] : mp2) ans.push_back(vec);
sort(ans.begin(), ans.end(), [&](const vector<int> &vec1, const vector<int> &vec2){return vec1[0] < vec2[0];});
cout << ans.size() << endl;
for (vector<int> vec : ans){
for (int x : vec) cout << x << ' ';
cout << endl;
}
return 0;
}