题目
点双联通图:若一个无向图删去任意一个点及与其相连的边,这个无向图仍联通,则这个无向图是点双连通图。
分析
首先求出 无向连通图 的 EGF(指数型生成函数) F ( x ) F(x) F(x)([集训队作业 2013] 城市规划),那么 有根无向连通图(下文简称 有根连通图) 的 EGF 为 R ( x ) = ∑ i = 0 n i f i x i R(x) = \sum_{i = 0}^n if_ix^i R(x)=∑i=0nifixi。设 有根点双连通图 的 EGF 为 G ( x ) G(x) G(x),现在尝试构造 G ( x ) G(x) G(x) 与 R ( x ) R(x) R(x) 的关系。(无向图中任意一个点都可被钦定为这个图的根)
对于一个 有根连通图,根肯定在至少一个点双(一个点也是一个点双)中,取任意一个根所在的 极大点双 记为 P P P,剩下的点都分成了多个 有根连通图 T i T_i Ti 挂在 P P P 上。
例如:
- 当 P = { 1 , 2 , 3 , 4 , 5 } P = \{1, 2, 3, 4, 5\} P={1,2,3,4,5}(第一个点为根,下同)时,剩下的两个 有根连通图 T 1 = { 1 , 9 , 10 , 11 , 12 } , T 2 = { 2 , 6 , 7 , 8 } , T 3 = { 4 , 13 } T_1 = \{1, 9, 10, 11, 12\}, T_2 = \{2, 6, 7, 8\}, T_3 = \{4, 13\} T1={1,9,10,11,12},T2={2,6,7,8},T3={4,13} 分别接在 1 1 1、 2 2 2 和 4 4 4 上;
- 当 P = { 1 , 9 , 11 } P = \{1, 9, 11\} P={1,9,11} 时,剩下的两个 有根连通图 T 1 = { 9 , 10 } , T 2 = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 12 , 13 } T_1 = \{9, 10\}, T_2 = \{1, 2, 3, 4, 5, 6, 7, 8, 12, 13\} T1={9,10},T2={1,2,3,4,5,6,7,8,12,13} 分别接在 9 9 9 和 1 1 1 上;
- 当 P = { 1 , 12 } P = \{1, 12\} P={1,12} 时,剩下的一个 有根连通图 T 1 = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 , 13 } T_1 = \{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13\} T1={1,2,3,4,5,6,7,8,9,10,11,13} 接在 1 1 1 上。
但如果我们枚举各个 P P P 和它上面挂的所有 T i T_i Ti,无疑会算重,就如同上面的三种情况,实际上是同一个图,这时候自然会发现重复的原因在于根所挂的 有向连通图,所以我们考虑不枚举根所在的 有向连通图:枚举根所在的所有 极大点双 P i P_i Pi,对于每个 P i P_i Pi,把一些 有根连通图 挂在 除根之外 的点上,再把这些点双一起接到根上,例如上图只会在一种情况下统计到(注意这里 T T T 的下标与 P P P 中点的对应关系,而上文中没有特意编排): P = { { P 1 = { 1 , 2 , 3 , 4 , 5 } , { T 1 = { 2 , 6 , 7 , 8 } , T 2 = ϕ , T 3 = { 4 , 13 } , T 4 = ϕ } } , { P 2 = { 1 , 9 , 11 } , { T 1 = { 9 , 10 } , T 2 = ϕ } } , { P 3 = { 1 , 12 } , { T 1 = ϕ } } } \begin{aligned} \mathcal{P} = \{&\{P_1 = \{1, 2, 3, 4, 5\}, &\{&T_1 = \{2, 6, 7, 8\}, &T_2 = &\phi, &T_3 = &\{4, 13\}, &T_4 = &\phi&\}&\}, \\ &\{P_2 = \{1, 9, 11\}, &\{&T_1 = \{9, 10\}, &T_2 = &\phi & & & & &\}&\},\\ &\{P_3 = \{1, 12\}, &\{&T_1 = \phi & & & & & & &\}&\}\} \end{aligned} P={{P1={1,2,3,4,5},{P2={1,9,11},{P3={1,12},{{{T1={2,6,7,8},T1={9,10},T1=ϕT2=T2=ϕ,ϕT3={4,13},T4=ϕ}}}},},}}
于是枚举根所在的 极大点双 大小,并把 有根连通图 接上去: ∑ i = 1 n g i + 1 R i ( x ) i ! {\sum_{i = 1}^{n} g_{i + 1} \frac{R^i(x)}{i!}} i=1∑ngi+1i!Ri(x) 枚举根,最后再把各个 极大点双 接到根上: R ( x ) = x exp ( ∑ i = 1 n g i + 1 R i ( x ) i ! ) R(x) = x \exp\left({\sum_{i = 1}^{n} g_{i + 1} \frac{R^i(x)}{i!}}\right) R(x)=xexp(i=1∑ngi+1i!Ri(x))
设
G
′
(
x
)
=
∑
i
=
1
n
g
i
+
1
⋅
x
i
i
!
G'(x) = \sum_{i = 1}^ng_{i + 1} \cdot \frac{x^i}{i!}
G′(x)=i=1∑ngi+1⋅i!xi (答案为
[
x
n
]
G
=
[
x
n
−
1
]
G
′
[x^n]G = [x^{n - 1}]G'
[xn]G=[xn−1]G′)那么
R
(
x
)
=
x
exp
G
′
(
R
(
x
)
)
R(x) = x \exp G'(R(x))
R(x)=xexpG′(R(x))
x
=
R
(
x
)
exp
G
′
(
R
(
x
)
)
x = \frac{R(x)}{\exp G'(R(x))}
x=expG′(R(x))R(x) 记
R
−
1
(
x
)
R^{-1}(x)
R−1(x) 为
R
(
x
)
R(x)
R(x) 的 复合逆,于是有
R
−
1
(
x
)
=
x
exp
G
′
(
x
)
R^{-1}(x) = \frac{x}{\exp G'(x)}
R−1(x)=expG′(x)x
G
′
(
x
)
=
ln
x
R
−
1
(
x
)
G'(x) = \ln \frac{x}{R^{-1}(x)}
G′(x)=lnR−1(x)x 这时你会以为做完了,然而 我们只能求出
R
−
1
(
x
)
R^{-1}(x)
R−1(x) 的一项!所以还要继续:
令
H
(
x
)
=
ln
R
(
x
)
x
H(x)=\ln \frac{R(x)}{x}
H(x)=lnxR(x) 则
G
′
(
x
)
=
H
(
R
−
1
(
x
)
)
G'(x) = H(R^{-1}(x))
G′(x)=H(R−1(x)) 再把
R
(
x
)
R(x)
R(x) 代入得
G
′
(
R
(
x
)
)
=
H
(
x
)
G'(R(x)) = H(x)
G′(R(x))=H(x) 扩展拉格朗日定理 的形式!于是
g
n
+
1
=
1
n
[
x
n
]
(
H
′
(
x
)
(
x
R
(
x
)
)
n
)
g_{n + 1} = \frac{1}{n} [x^n] \left( H'(x) \left( \frac{x}{R(x)} \right)^n \right)
gn+1=n1[xn](H′(x)(R(x)x)n) 注意这些都是 EGF,最后的答案为
g
n
⋅
n
!
g_n \cdot n!
gn⋅n!,代入计算即可。
代码
注意
n
=
1
n = 1
n=1 特判,因为
g
0
=
0
g_0 = 0
g0=0,但一个点也是点双。
代码中的变量名跟文章有出入。
#include <bits/stdc++.h>
#define RG register
typedef long long LL;
int Read() {
int x = 0; bool f = false; char c = getchar();
while (c < '0' || c > '9')
f |= c == '-', c = getchar();
while (c >= '0' && c <= '9')
x = (x * 10) + (c ^ 48), c = getchar();
return f ? -x : x;
}
template <const int _MOD> struct ModNumber { // 为了效率 (事实上还是不高) 省去了一些实用性
int x;
inline ModNumber() { x = 0; }
inline ModNumber(const int &y) { x = y; }
// 需保证 y 的范围! (如果在这 % _MOD 会 T, 因为代码中大量调用该构造函数)
// (当然, 开 O2 可以起飞)
inline int Int() { return x; }
inline ModNumber Pow(LL y) const {
RG int ret = 1, tmp = x;
while (y) {
if (y & 1) ret = ((LL)ret * tmp) % _MOD;
y >>= 1; tmp = ((LL)tmp * tmp) % _MOD;
}
return ModNumber(ret);
}
inline bool operator == (const ModNumber &y) const { return x == y.x; }
inline bool operator != (const ModNumber &y) const { return x != y.x; }
inline bool operator < (const ModNumber &y) const { return x < y.x; }
inline bool operator > (const ModNumber &y) const { return x > y.x; }
inline bool operator <= (const ModNumber &y) const { return x <= y.x; }
inline bool operator >= (const ModNumber &y) const { return x >= y.x; }
inline ModNumber operator + (const ModNumber &y) const { return (x + y.x >= _MOD) ? (x + y.x - _MOD) : (x + y.x); }
inline ModNumber operator - (const ModNumber &y) const { return (x - y.x < 0) ? (x - y.x + _MOD) : (x - y.x); }
inline ModNumber operator * (const ModNumber &y) const { return ModNumber((LL)x * y.x % _MOD); }
inline ModNumber operator / (const ModNumber &y) const { return *this * y.Pow(_MOD - 2); }
inline ModNumber operator ^ (const LL &y) const { return Pow(y); }
inline void operator += (const ModNumber &y) { *this = *this + y; }
inline void operator *= (const ModNumber &y) { *this = *this * y; }
inline void operator -= (const ModNumber &y) { *this = *this - y; }
inline void operator /= (const ModNumber &y) { *this = *this / y; }
inline void operator ^= (const LL &y) const { *this = *this ^ y; }
};
const int MAXN = 100000 * 4; // 所有 MAXN 都要开 4 倍!
const int MOD = 998244353;
typedef ModNumber<MOD> Int;
const Int __G = 3, One = 1, Two = 2, InvTwo = One / Two;
namespace Polynomial {
// 好氧, 好氧, 好氧! (无氧可能原地去世)
// 所有 n: 多项式的项数 (即次数 + 1)
// 数组从 0 开始存
int Rev[MAXN + 5];
Int G0[2][MAXN + 5];
void GetG0(const int &n) { // 使用前必须先初始化 G0
for (RG int i = 2; i <= n; i <<= 1) {
G0[0][i] = __G ^ ((MOD - 1) / i);
G0[1][i] = G0[0][i] ^ (MOD - 2);
}
}
inline void GetRev(const int n) { // Rev 在函数内初始化
for (RG int i = 0; i < n; i++)
Rev[i] = (Rev[i >> 1] >> 1) | ((i & 1) * (n >> 1));
}
inline int ToPow(const int &n) { // lim 在函数内初始化
RG int ret = 1;
while (ret < n)
ret <<= 1;
return ret;
}
void PrintPoly(Int *A, const int &n) {
for (RG int i = 0; i < n; i++)
printf("%d ", A[i].x);
puts("");
}
void ReadPoly(Int *A, const int &n) {
for (RG int i = 0; i < n; i++)
A[i].x = Read();
}
void NTT(Int *A, const int &n, const int &opt) {
for (RG int i = 0; i < n; i++)
if (i < Rev[i])
std::swap(A[i], A[Rev[i]]);
for (RG int mid = 1; mid < n; mid <<= 1) {
const int k = mid << 1;
const Int g0 = G0[opt][k];
for (RG int i = 0; i < n; i += k) {
Int g = 1;
for (RG int j = 0; j < mid; j++, g *= g0) {
Int tmp1 = A[i + j], tmp2 = A[i + j + mid] * g;
A[i + j] = tmp1 + tmp2, A[i + j + mid] = tmp1 - tmp2;
}
}
}
if (opt == 1) {
const Int inv = One / n;
for (RG int i = 0; i < n; i++)
A[i] *= inv;
}
}
Int A0[MAXN + 5], B0[MAXN + 5];
void Multiply(const Int *A, const Int *B, Int *P, const int &n, const int &m) { // P = A * B
int lim = ToPow(n + m - 1);
GetRev(lim);
for (int i = 0; i < lim; i++)
A0[i] = B0[i] = 0;
for (RG int i = 0; i < n; i++)
A0[i] = A[i];
for (RG int i = 0; i < m; i++)
B0[i] = B[i];
NTT(A0, lim, 0), NTT(B0, lim, 0);
for (RG int i = 0; i < lim; i++)
P[i] = A0[i] * B0[i];
NTT(P, lim, 1);
}
Int A1[MAXN + 5], B1[MAXN + 5], C1[MAXN + 5];
void Inverse(const Int *A, Int *B, const int &n) { // B = 1 / A, A 不变
if (n == 1) {
B[0] = A[0] ^ (MOD - 2);
return;
}
Inverse(A, B, (n + 1) >> 1);
const int lim = ToPow(n + n - 1);
GetRev(lim);
for (RG int i = 0; i < n; i++)
A1[i] = A[i], B1[i] = B[i];
for (RG int i = n; i < lim; i++)
A1[i] = B1[i] = 0;
NTT(A1, lim, 0), NTT(B1, lim, 0);
for (RG int i = 0; i < lim; i++)
C1[i] = A1[i] * B1[i] * B1[i];
NTT(C1, lim, 1);
for (RG int i = 0; i < n; i++)
B[i] = Two * B[i] - C1[i];
for (RG int i = n; i < lim; i++)
B[i] = 0;
}
Int C2[MAXN + 5], InvB[MAXN + 5], B2[MAXN + 5];
void Sqrt(const Int *A, Int *B, const int &n) { // B = √A, A 不变 (默认开根的多项式常数项为 1)
if (n == 1) {
B[0] = 1;
return;
}
Sqrt(A, B, (n + 1) >> 1);
Inverse(B, InvB, n);
Multiply(B, B, B2, n, n);
for (RG int i = 0; i < n; i++)
InvB[i] *= InvTwo;
for (RG int i = 0; i < n; i++)
B2[i] += A[i];
Multiply(B2, InvB, C2, n, n);
for (RG int i = 0; i < n; i++)
B[i] = C2[i];
}
Int AR[MAXN + 5], BR[MAXN + 5], InvBR[MAXN + 5], A2[MAXN + 5], CR[MAXN + 5], C3[MAXN + 5];
void Divide(const Int *A, const Int *B, Int *C, Int *R, const int &n, const int &m) { // C = A / B, R = A % B, A 不变, B 不变
for (RG int i = 0; i < n; i++)
AR[i] = A[n - i - 1], A2[i] = A[i];
for (RG int i = 0; i < n - m + 1; i++)
BR[i] = B[m - i - 1];
Inverse(BR, InvBR, n - m + 1);
Multiply(AR, InvBR, CR, n, n - m + 1);
for (int i = 0; i < n - m + 1; i++)
C[i] = CR[n - m - i];
Multiply(B, C, C3, m, n - m + 1);
for (RG int i = 0; i < m - 1; i++)
R[i] = A[i] - C3[i];
}
void Derivative(const Int *A, Int *B, const int &n) { // B = A', A 不变
for (RG int i = 0; i < n - 1; i++)
B[i] = A[i + 1] * (i + 1);
B[n - 1] = 0;
}
void Integral(const Int *A, Int *B, const int &n) { // B = ∫A, A 不变
for (RG int i = 1; i < n; i++)
B[i] = A[i - 1] / i;
B[0] = 0;
}
Int AD[MAXN + 5], InvA[MAXN + 5], C4[MAXN + 5];
void Ln(const Int *A, Int *B, const int &n) { // B = ln A, A 不变
Derivative(A, AD, n);
Inverse(A, InvA, n);
Multiply(AD, InvA, C4, n, n);
Integral(C4, B, n);
}
Int LnB[MAXN + 5], B3[MAXN + 5], B4[MAXN + 5];
void Exp(const Int *A, Int *B, const int &n) { // B = e^A, A 不变
if (n == 1) {
B[0] = 1;
return;
}
Exp(A, B, (n + 1) >> 1);
Ln(B, LnB, n);
const int lim = ToPow(n + n - 1);
for (int i = 0; i < n; i++)
B3[i] = A[i] - LnB[i], B4[i] = B[i];
B3[0] += One;
Multiply(B3, B4, B, n, n);
for (int i = n; i < lim; i++)
B[i] = 0;
}
Int KA[MAXN + 5], LnA[MAXN + 5];
void Pow(const Int *A, Int *B, const int &n, const Int &k) { // B = A^k, A 不变
Polynomial::Ln(A, LnA, n);
for (int i = 0; i < n; i++)
KA[i] = LnA[i] * k;
Exp(KA, B, n);
}
Int A3[MAXN + 5], AP[MAXN + 5], C5[MAXN + 5];
Int CompoundInverse(const Int *A, const int &k, const Int *C = NULL) { // A(B(x)) = x 或 A(B(x)) = C(x), 返回 [x^k]B
for (int i = 0; i < k; i++)
A3[i] = A[i + 1];
Pow(A3, AP, k, k);
Inverse(AP, A3, k);
if (C == NULL)
return A3[k - 1] / k;
Derivative(A3, AP, k);
Multiply(AP, C, C5, k, k);
return C5[k - 1] / k;
}
}
int N;
Int Fac[MAXN + 5], Inv[MAXN + 5];
Int G[MAXN + 5], D[MAXN + 5], H[MAXN + 5], D1[MAXN + 5], HD[MAXN + 5], HE[MAXN + 5], H1[MAXN + 5];
int main() {
Polynomial::GetG0(MAXN);
N = MAXN / 4;
Fac[0] = 1;
for (int i = 1; i <= N; i++)
Fac[i] = Fac[i - 1] * i;
Inv[N] = One / Fac[N];
for (int i = N - 1; i >= 0; i--)
Inv[i] = Inv[i + 1] * (i + 1);
int T = 5;
while (T--) {
if ((N = Read()) == 1) {
puts("1");
continue;
}
G[0] = 1;
for (int i = 1; i <= N; i++)
G[i] = (Two ^ ((LL)i * (i - 1) / 2)) * Inv[i];
Polynomial::Ln(G, D, N + 1);
for (int i = 0; i < N; i++)
D1[i] = D[i + 1] * (i + 1);
Polynomial::Ln(D1, H, N);
Polynomial::Derivative(H, HD, N);
for (int i = 0; i < N; i++)
H[i] *= (MOD - N + 1);
Polynomial::Exp(H, HE, N);
Polynomial::Multiply(HD, HE, H1, N, N);
Int Ans = H1[N - 2] / (N - 1) * Fac[N - 1];
printf("%d\n", Ans.x);
}
return 0;
}