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)为树枝边
} \} }
证明 可归纳证明,每次优先选择路径上有至少两个支链的叶子结点合并。
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;
}
}
证明 对于回路 C C C,容易验证 ω ( C − S ) ≤ ∣ S ∣ \omega(C - S)\le |S| ω(C−S)≤∣S∣,故
ω ( G − S ) ≤ ω ( C − S ) ≤ ∣ S ∣ \omega(G-S)\le \omega(C-S)\le|S| ω(G−S)≤ω(C−S)≤∣S∣
证明 设 G G G 符合该条件且不是哈密尔顿图,通过给 G G G 加边可以得到一个边数最大且不是哈密尔顿图的图 G ′ G' G′。假设给 G ′ G' G′ 中不相邻的两点 v 1 , v n v_1,v_n v1,vn 加边后会得到一个哈密尔顿图,则 G ′ G' G′ 中 v 1 v_1 v1 到 v n v_n vn 的路径 v 1 , v 2 , … , v n v_1,v_2,\dots,v_n v1,v2,…,vn 中必存在两个点 v i , v i + 1 v_i,v_{i+1} vi,vi+1,使得 v 1 v_1 v1 与 v i + 1 v_{i + 1} vi+1 相邻, v i v_i vi 与 v n v_n vn 相邻,否则若 v 1 v_1 v1 与 v i 1 , v i 2 , … , v i k v_{i_1},v_{i_2},\dots,v_{i_k} vi1,vi2,…,vik 相邻, v n v_n vn 与 v i 1 − 1 , v i 2 − 1 , … , v i k − 1 v_{i_1-1},v_{i_2-1},\dots,v_{i_k-1} vi1−1,vi2−1,…,vik−1 均不相邻,则 d e g ( v n ) + d e g ( v 1 ) ≤ n − 1 deg(v_n)+deg(v_1)\le n - 1 deg(vn)+deg(v1)≤n−1,与题设条件矛盾。则可以找到一条哈密顿回路 v 1 , v i + 1 , v i + 2 , … , v n , v i , v i − 1 , … , v 1 v_1,v_{i+1},v_{i+2},\dots,v_n,v_i,v_{i - 1},\dots,v_1 v1,vi+1,vi+2,…,vn,vi,vi−1,…,v1,与 G ′ G' G′ 的前提条件矛盾,故 G G G 不存在。
设简单无向图 G = < V , E > G =
若能判定一个度数序列是哈密尔顿序列,则可以判定所有相关的图都是哈密尔顿图。
Chvátal 定理 若 ∀ i < n 2 , d i ≤ i ⇒ d n − i ≥ n − i \forall i < \dfrac{n}{2}, d_i \le i \Rightarrow d_{n-i} \ge n - i ∀i<2n,di≤i⇒dn−i≥n−i,度数序列 [ d 1 , d 2 , … , d n ] [d_1,d_2,\dots,d_n] [d1,d2,…,dn] 是哈密尔顿序列。
证明 设 G G G 符合该条件且不是哈密尔顿图,通过给 G G G 加边可以得到一个边数最大且不是哈密尔顿图的图 G ′ G' G′,使得 G ′ G' G′ 存在两个不相邻的点 v 1 , v n v_1,v_n v1,vn,满足 d ( v 1 ) + d ( v n ) d(v_1)+d(v_n) d(v1)+d(vn) 尽可能大且 d ( v 1 ) ≤ d ( v n ) d(v_1)\le d(v_n) d(v1)≤d(vn),且增加边 ( v 1 , v n ) (v_1,v_n) (v1,vn) 后会得到一个哈密尔顿图,设 v 1 v_1 v1 到 v n v_n vn 的路径为 v 1 , v 2 , … , v n v_1, v_2, \dots, v_n v1,v2,…,vn。
设 A = { i ∣ ( v 1 , v i + 1 ) ∈ G ′ } , B = { i ∣ ( v i , v n ) ∈ G ’ } A = \{i|(v_1,v_{i+1})\in G'\},B=\{i|(v_i,v_n)\in G’\} A={i∣(v1,vi+1)∈G′},B={i∣(vi,vn)∈G’},若 A , B A,B A,B 有交,则存在 v 1 v_1 v1 与 v i + 1 v_{i+ 1} vi+1 相邻, v i v_i vi 与 v n v_n vn 相邻,由 充分条件 的证明可构造出哈密尔顿回路,故 A , B A,B A,B 无交集且 d e g ( v 1 ) + d e g ( v n ) < n deg(v_1)+deg(v_n) < n deg(v1)+deg(vn)<n,进一步有 d e g ( v 1 ) = ∣ A ∣ < n 2 deg(v_1)=|A|<\dfrac{n}{2} deg(v1)=∣A∣<2n。
注意到 v 1 v_1 v1 是不与 v n v_n vn 相邻的度数最大的点,且由 A , B A,B A,B 无交集, v n v_n vn 至少与所有 v i ( i ∈ A ) v_i(i\in A) vi(i∈A) 不相邻,故至少有 ∣ A ∣ |A| ∣A∣ 个度数至少为 ∣ A ∣ |A| ∣A∣ 的点,由题设条件可知至少有 ∣ A ∣ + 1 |A|+1 ∣A∣+1 个度数至少为 n − ∣ A ∣ n - |A| n−∣A∣ 的点,由鸽巢原理其中至少有一个点与 v 1 v_1 v1 不相邻,但两者的度数之和为 n n n,与 d ( v 1 ) + d ( v n ) d(v_1)+d(v_n) d(v1)+d(vn) 是能取到的最大值矛盾,故图 G G G 不存在。
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 序列双射对应一个生成树。
证明 设将第 i i i 个连通块作为一个整体时的度数为 d i ( d i ≥ 0 ) d_i(d_i \ge 0) di(di≥0),先不考虑由具体哪个内部结点连边,总方案数为:
∑ ∑ i = 1 k d i = 2 k − 2 ( k − 2 d 1 − 1 d 2 − 1 … d k − 1 ) ∏ i = 1 k a i d i \sum \limits_{\sum \limits_{i = 1}^{k}d_i = 2k - 2} \binom{k - 2}{d_1 - 1\ d_2 - 1\ \dots\ d_{k} - 1}\prod\limits_{i = 1}^ka_i^{d_i} i=1∑kdi=2k−2∑(d1−1 d2−1 … dk−1k−2)i=1∏kaidi
设 e i = d i − 1 e_i = d_i - 1 ei=di−1,通过多元二项式定理进行代换,得到:
∑ ∑ i = 1 k e i = k − 2 ( k − 2 e 1 e 2 … e k ) ∏ i = 1 k a i e i + 1 = ( ∑ i = 1 k a i ) k − 2 ∑ i = 1 k a i = n k − 2 ∑ i = 1 k a i \sum \limits_{\sum \limits_{i = 1}^{k}e_i = k - 2} \binom{k - 2}{e_1\ e_2\ \dots\ e_{k}}\prod\limits_{i = 1}^ka_i^{e_i+1} =\left(\sum \limits_{i = 1}^{k}a_i\right)^{k - 2}\sum \limits_{i = 1}^{k}a_i =n^{k - 2}\sum \limits_{i = 1}^{k}a_i i=1∑kei=k−2∑(e1 e2 … ekk−2)i=1∏kaiei+1=(i=1∑kai)k−2i=1∑kai=nk−2i=1∑kai
inline void dfs1(int x)
{
f[x] = sze[x] = 1;
for (int y : e[x])
{
dfs1(y);
f[x] = (1ll * f[y] * pri[sze[y]] + f[x]) % mod;
sze[x] += sze[y];
}
}
inline void dfs2(int x)
{
for (int y : e[x])
{
int tmp = f[x];
add(tmp, g[x]);
dec(tmp, 1ll * f[y] * pri[sze[y]] % mod);
g[y] = 1ll * tmp * pri[_n - sze[y]] % mod;
dfs2(y);
}
add(f[x], g[x]);
}
const int N = 1e5 + 5;
const int M = 2e5 + 5;
vector<int> e[N], o[N];
int px[M], py[M], deg[N], vis[N];
inline bool cmp(const int &x, const int &y)
{
return deg[x] < deg[y] || deg[x] == deg[y] && x < y;
}
inline int countCycle3()
{
int res = 0;
for (int i = 1; i <= m; ++i)
++deg[px[i]], ++deg[py[i]];
for (int i = 1; i <= m; ++i)
{
if (!cmp(px[i], py[i]))
std::swap(px[i], py[i]);
e[px[i]].emplace_back(py[i]);
}
for (int x = 1, y; x <= n; ++x)
{
for (int y : e[x])
vis[y] = x;
for (int y : e[x])
for (int z : e[y])
res += vis[z] == x;
}
return res;
}
const int N = 1e5 + 5;
const int M = 2e5 + 5;
vector<int> e[N], o[N];
int px[M], py[M], deg[N], vis[N];
inline bool cmp(const int &x, const int &y)
{
return deg[x] < deg[y] || deg[x] == deg[y] && x < y;
}
inline int countCycle4()
{
ll res = 0;
for (int i = 1; i <= m; ++i)
{
++deg[px[i]], ++deg[py[i]];
o[px[i]].emplace_back(py[i]);
o[py[i]].emplace_back(px[i]);
}
for (int i = 1; i <= m; ++i)
{
if (!cmp(px[i], py[i]))
std::swap(px[i], py[i]);
e[px[i]].emplace_back(py[i]);
}
for (int x = 1, y; x <= n; ++x)
{
for (int y : o[x])
for (int z : e[y])
if (cmp(x, z))
res += vis[z]++;
for (int y : o[x])
for (int z : e[y])
if (cmp(x, z))
vis[z] = 0;
}
return res;
}
inline void dfs(int x)
{
dfn[x] = ++tis;
dep[x] = dep[fa[x]] + 1;
f[0][dfn[x]] = fa[x];
for (arc *e = adj[x]; e; e = e->nxt)
{
int y = e->to;
if (y == fa[x])
continue ;
fa[y] = x;
dfs(y);
}
}
inline int queryLCA(int x, int y)
{
if (x == y)
return x;
x = dfn[x], y = dfn[y];
if (x > y) std::swap(x, y);
++x;
int k = Log[y - x + 1];
return depMin(f[k][x], f[k][y - (1 << k) + 1]);
}
inline void init()
{
Log[0] = -1;
for (int i = 1; i <= n; ++i)
Log[i] = Log[i >> 1] + 1;
dfs(rt);
for (int j = 1; j <= Log[n]; ++j)
for (int i = 1; i + (1 << j) - 1 <= n; ++i)
f[j][i] = depMin(f[j - 1][i], f[j - 1][i + (1 << j - 1)]);
}
vector
和 pair
实测常数较大。inline int ufs_find(int x)
{
if (fa[x] != x)
return fa[x] = ufs_find(fa[x]);
return x;
}
inline void Tarjan(int x)
{
fa[x] = x;
vis[x] = true;
for (arc *e = adj[x]; e; e = e->nxt)
{
int y = e->to;
if (vis[y])
continue ;
Tarjan(y);
fa[y] = x;
}
for (pir e : query[x])
if (vis[e.first])
ans[e.second] = ufs_find(e.first);
}
证明 分类讨论即可。