l o w [ x ] = min low[x] = \min low[x]=min
{ \{ {
d f n [ x ] dfn[x] dfn[x]
l o w [ y ] , ( x , y ) 为树枝边, x 为 y 的父结点 low[y], (x, y) 为树枝边,x 为 y 的父结点 low[y],(x,y)为树枝边,x为y的父结点
d f n [ y ] , ( x , y ) 为后向边或指向栈中结点的横叉边 dfn[y], (x, y) 为后向边或指向栈中结点的横叉边 dfn[y],(x,y)为后向边或指向栈中结点的横叉边
} \} }
inline void Tarjan(int x)
{
dfn[x] = low[x] = ++tis;
stk[++top] = x;
ins[x] = true;
int y;
for (arc *e = adj[x]; e; e = e->nxt)
if (!dfn[y = e->to])
{
Tarjan(y);
CkMin(low[x], low[y]);
}
else if (ins[y])
CkMin(low[x], dfn[y]);
if (dfn[x] == low[x])
{
++C;
do
{
y = stk[top--];
ins[y] = false;
col[y] = C;
}while (y != x);
}
}
l o w [ x ] = min low[x] = \min low[x]=min
{ \{ {
d f n [ x ] dfn[x] dfn[x]
d f n [ y ] , ( x , y ) 为后向边 dfn[y], (x,y) 为后向边 dfn[y],(x,y)为后向边
l o w [ y ] , ( x , y ) 为树枝边 low[y], (x,y) 为树枝边 low[y],(x,y)为树枝边
} \} }
对于一个有桥的连通图,求加最少数量的边,使其变为边双连通图。
用 Tarjan \text{Tarjan} Tarjan 求出边双,将边双缩为一点,则原图变为一颗树。
记这棵无根树中度为 1 1 1 的点数为 l e a f leaf leaf,则所加最少边数为 ⌊ l e a f + 1 2 ⌋ \lfloor\frac{leaf + 1}{2}\rfloor ⌊2leaf+1⌋。
inline void Tarjan(int x)
{
dfn[x] = low[x] = ++tis;
int y;
for (int e = adj[x]; e; e = nxt[e])
{
if (e == (up[x] ^ 1)) //树枝边的反相边要注意判断
continue;
if (!dfn[y = to[e]])
{
up[y] = e;
Tarjan(y);
CkMin(low[x], low[y]);
if (dfn[x] < low[y])
bridge[e] = bridge[e ^ 1] = true;
}
else
CkMin(low[x], dfn[y]);
}
}
证明 若某点在偶环上,则一定存在一个偶环与已知的奇环有公共边,则可将偶环和已知的奇环合并得到一个新的奇环。
#include
#include
template <class T>
inline void read(T &res)
{
char ch;
while (ch = getchar(), !isdigit(ch));
res = ch ^ 48;
while (ch = getchar(), isdigit(ch))
res = res * 10 + ch - 48;
}
template <class T>
inline void CkMin(T &x, T y) {x > y ? x = y : 0;}
const int N = 1e3 + 5;
const int M = 2e6 + 5;
int fa[N], col[N], dfn[N], low[N], stkx[M], stky[M];
int tis, n, m, top;
bool edge[N][N], inc[N], ans[N];
struct arc
{
int to;
arc *nxt;
}p[M], *adj[N], *T = p;
inline void linkArc(int x, int y)
{
(++T)->nxt = adj[x]; adj[x] = T; T->to = y;
(++T)->nxt = adj[y]; adj[y] = T; T->to = x;
}
inline bool dfsColoring(int x)
{
for (arc *e = adj[x]; e; e = e->nxt)
{
int y = e->to;
if (col[y] == -1)
{
col[y] = col[x] ^ 1;
if (!dfsColoring(y))
return false;
}
else if (col[y] == col[x])
return false;
}
return true;
}
inline void solvePBC(int x, int y)
{
T = p;
for (int i = 1; i <= n; ++i)
adj[i] = NULL;
int u, v;
do
{
u = stkx[top], v = stky[top];
linkArc(u, v);
inc[u] = inc[v] = true;
--top;
}while (x != u || y != v);
for (int i = 1; i <= n; ++i)
col[i] = -1;
col[x] = 0;
if (!dfsColoring(x))
{
for (int i = 1; i <= n; ++i)
if (inc[i])
ans[i] = true;
}
for (int i = 1; i <= n; ++i)
inc[i] = false;
}
inline void Tarjan(int x)
{
dfn[x] = low[x] = ++tis;
for (int y = 1; y <= n; ++y)
{
if (y == fa[x] || !edge[x][y])
continue;
if (dfn[y] < dfn[x]) // 包含条件 !dfn[y]
{
++top;
stkx[top] = x;
stky[top] = y;
}
if (!dfn[y])
{
fa[y] = x;
Tarjan(y);
CkMin(low[x], low[y]);
if (dfn[x] <= low[y])
solvePBC(x, y);
}
else
CkMin(low[x], dfn[y]);
}
}
int main()
{
while (1)
{
read(n); read(m);
if (!n && !m)
break ;
for (int i = 1; i <= n; ++i)
ans[i] = false;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
edge[i][j] = i == j ? false : true;
for (int i = 1, x, y; i <= m; ++i)
{
read(x); read(y);
edge[x][y] = edge[y][x] = false;
}
tis = top = 0;
for (int i = 1; i <= n; ++i)
dfn[i] = low[i] = fa[i] = 0;
for (int i = 1; i <= n; ++i)
if (!dfn[i])
Tarjan(i);
int fans = 0;
for (int i = 1; i <= n; ++i)
fans += !ans[i];
printf("%d\n", fans);
}
return 0;
}
inline void findCircuit(int x)
{
for (int &e = adj[x]; e; e = nxt[e])
if (!vis[e])
{
int c = e;
vis[c] = true;
if (type & 1) // type = 1 为无向图,type = 0 为有向图
vis[c ^ 1] = true;
findCircuit(to[c]);
stk[++top] = c;
}
}
inline void TreeToPrufer()
{
for (int i = 1, x; i < n; ++i)
{
read(fa[i]);
++deg[fa[i]];
}
// 这里的 fa[i] 指以 n 为根时结点 i 的父结点
int p = 1, x = 0;
for (int i = 1; i <= n - 2; ++i)
if (x && x < p)
{
ans[i] = fa[x];
x = !--deg[fa[x]] ? fa[x] : 0;
}
else
{
while (deg[p])
++p;
ans[i] = fa[p];
x = !--deg[fa[p]] ? fa[p] : 0;
++p;
}
}
inline void PruferToTree()
{
for (int i = 1; i <= n - 2; ++i)
{
read(ans[i]);
++deg[ans[i]];
}
int p = 1, x = 0;
for (int i = 1; i <= n - 2; ++i)
if (x && x < p)
{
fa[x] = ans[i];
x = !--deg[ans[i]] ? ans[i] : 0;
}
else
{
while (deg[p])
++p;
fa[p] = ans[i];
x = !--deg[ans[i]] ? ans[i] : 0;
++p;
}
for (int i = 1; i < n; ++i)
if (!fa[i])
{
fa[i] = n;
break ;
}
}
证明 由构造和还原过程可知,任意一个长度为 n − 2 n - 2 n−2、值域为 [ 1 , n ] [1,n] [1,n] 的整数序列都可以通过 Prüfer 序列双射对应一个生成树。
证明 待补充。