【图论】边双连通分量
不存在桥的图,称为边双连通图。原图可以经过tarjan算法求出桥,桥就连接了两个边双连通分量,假如把桥的都堵住(注意是只能把桥堵住,不能堵住点),然后每一块找一次连通块,就可以找到每个点所属的双连通分量,然后可以缩点成一棵树。
struct EBCC {
static const int MAXN = 2e5 + 10;
static const int MAXM = 2e6 + 10;
int n, m, t;
struct Edge {
int v, nxt;
} e[MAXM << 1];
int h[MAXN];
int dfn[MAXN], low[MAXN];
bool brg[MAXM];
int col[MAXN];
vector<int> ebcc[MAXN];
void Init(int n) {
this->n = n, m = 1;
for(int i = 1; i <= n; ++i)
h[i] = 0;
}
void AddEdge(int u, int v) {
if(u == v) {
m += 2;
return;
}
e[++m] = {v, h[u]}, h[u] = m;
e[++m] = {u, h[v]}, h[v] = m;
}
void dfs(int u, int from) {
dfn[u] = low[u] = ++t;
for(int i = h[u]; i; i = e[i].nxt) {
int v = e[i].v;
if(!dfn[v]) {
dfs(v, i ^ 1);
low[u] = min(low[u], low[v]);
if(low[v] > dfn[u])
brg[i / 2] = 1;
} else if(i != from)
low[u] = min(low[u], dfn[v]);
}
}
void GetBridges() {
t = 0;
for(int i = 1; i <= n; ++i)
dfn[i] = low[i] = 0;
for(int i = 2; i <= m; i += 2)
brg[i / 2] = 0;
for(int i = 1; i <= n; ++i) {
if(!dfn[i])
dfs(i, 0);
}
}
void dfs2(int u, int color) {
col[u] = color, ebcc[color].eb(u);
for(int i = h[u]; i; i = e[i].nxt) {
if(brg[i / 2])
continue;
int v = e[i].v;
if(!col[v])
dfs(v, color);
}
}
void GetEBCC() {
GetBridges();
for(int i = 1; i <= n; ++i)
col[i] = 0, ebcc[i].clear();
int n2 = 0;
for(int i = 1; i <= n; ++i) {
if(!col[i]) {
++n2;
dfs2(i, n2);
}
}
for(int i = 2; i <= m; i += 2) {
if(brg[i / 2]) {
int u = e[i].v, v = e[i + 1].v;
// AddBridge(u,v)
}
}
}
} ebcc;
问题:加最少的边,使得无向图变成边双连通图。
首先,缩点。然后特判剩下的树是否只有1个点,是就返回。
否则,从根开始dfs给叶子们编号。假如树有cnt个叶子,若cnt为偶数,那么第i个叶子和第i+cnt/2个叶子连边(保证至少有一对点的LCA跨越根,并且根的每条出边都被跨越)。 但是为什么呢。dfs序中,一个子树是dfs区间是连续的。
有的算法是说找每次LCA最高的两个叶子连接(例如Claris的),但是这个是错的,例如一个“天”字形的图。