BEST定理学习笔记
内容:
对于一张有向图,以 \(s\) 为起点,它的欧拉回路数量为
\[det_sdeg_s!\prod (deg_u-1)![u \neq s] = det_sdeg_s\prod (deg_u-1)!
\]
\(det_s\) 表示以 \(s\) 为根的有根生成树个数 (可以是内向树也可以是外向树), \(deg_u\) 表示点 \(u\) 的度数(可以是入度也可以是出度).
证明:
对于一棵有根生成树,不妨设它是内向树,给它的每一条边标一个顺序,其中指向根的边顺序最靠后,方案数显然是 \(deg_s!\prod (deg_u-1)![u \neq s]\), 再乘上有根生成树的个数就是 \(det_sdeg_s!\prod (deg_u-1)![u \neq s]\), 接着就是证明每种标号方案对应一条欧拉回路.
每经过一条边后,删掉这条边,只需证明剩下的边存在欧拉路径,即剩下的边满足如下条件。
- 图弱联通
- 有两个点入度和出度相差 \(1\), 其它点入度等于出度
若删去的边 \((u, v)\) 是非树边,由于树边是每个点最后离开的边 \((u, v)\) 之间的树边一定还没有被删去,则 \(u\) 到 \(v\) 之间可以直接通过树边形成弱连通;
若删去的边是树边,则删去后 \(u\) 与剩下的图完全断开,相当于删去了一条链末端的一条边,剩余边仍然弱连通。
所以删掉一条边后剩下的图仍然有欧拉路径.
Code:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int mod = 1e6 + 3;
const int N = 110;
int A[N][N];
int degin[N], degout[N];
int T, n;
int fac[300000], inv[300000], ifac[300000];
int qpow(int a, int b) {
int res = 1;
while (b) {
if (b & 1) res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res;
}
int add(int a, int b) {
return a + b >= mod ? a + b - mod : a + b;
}
int sub(int a, int b) {
return a - b < 0 ? a - b + mod : a - b;
}
int det(int n) {
int res = 1;
for (int i = 2; i <= n; ++i) {
if (!degout[i]) continue;
if (!A[i][i]) {
for (int j = i + 1; j <= n; ++j) {
if (A[j][i]) {
swap(A[i], A[j]);
res = -res;
break;
}
}
}
for (int j = i + 1; j <= n; ++j) {
int div = 1ll * A[j][i] * qpow(A[i][i], mod - 2) % mod;
for (int k = i; k <= n; ++k) {
A[j][k] = sub(A[j][k], 1ll * div * A[i][k] % mod);
}
}
}
res = (mod + res) % mod;
for (int i = 2; i <= n; ++i) {
res = 1ll * res * A[i][i] % mod;
}
return res;
}
signed main() {
scanf("%d", &T);
fac[0] = fac[1] = ifac[0] = ifac[1] = inv[1] = 1;
for (int i = 2; i <= 220000; ++i) {
fac[i] = 1ll * fac[i - 1] * i % mod;
inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
ifac[i] = 1ll * ifac[i - 1] * inv[i] % mod;
}
while (T--) {
scanf("%d", &n);
memset(A, 0, sizeof(A));
memset(degin, 0, sizeof(degin));
memset(degout, 0, sizeof(degout));
for (int i = 1; i <= n; ++i) {
int s;
scanf("%d", &s);
for (int j = 1; j <= s; ++j) {
int x;
scanf("%d", &x);
A[i][x] = sub(A[i][x], 1);
++degin[x];
++degout[i];
}
A[i][i] = add(A[i][i], s);
}
if (n == 1) {
puts("1");
continue;
}
int flag = 0;
for (int i = 1; i <= n; ++i) {
if (degin[i] != degout[i]) {
flag = 1;
}
}
if (flag == 1) {
puts("0");
continue;
}
int res = det(n);
res = 1ll * res * fac[degout[1]] % mod;
for (int i = 2; i <= n; ++i) {
res = 1ll * res * fac[degout[i] - 1] % mod;
}
printf("%d\n", res);
}
return 0;
}