【题目大意】
- 如果一个竞赛图含有哈密顿回路,则称这张竞赛图为值得记录的
- 从所有含有 n n n个顶点(顶点互不相同)的,值得记录的竞赛图中等概率随机选取一个
- 求选取的竞赛图中哈密顿回路数量的期望
- 输出答案除以 998244353 998244353 998244353的余数
- 竞赛图:指任意两个顶点间恰有一条有向边的有向图
- 哈密顿回路:指除起点和终点外经过所有顶点恰好一次且起点和终点相同的路径
【算法分析】
- 组合数学 + + + 多项式求逆 + + + d p dp dp
- 哈密顿回路数量的期望 = = = 所有竞赛图的哈密顿回路总数 / / / 值得记录的竞赛图个数
- 显然哈密顿回路总数 = = = 2 C ( n , 2 ) − n ∗ ( n − 1 ) ! 2^{C(n,2)-n}*(n-1)! 2C(n,2)−n∗(n−1)!
- 即考虑 n n n个点的 n − 1 n-1 n−1种圆排列,每个点往排列中的下一个点连边,排列中的最后一个点往第一个点连边
- 总共有 C ( n , 2 ) C(n,2) C(n,2)条边,确定一个圆排列,就确定了 n n n条边的方向,剩下 C ( n , 2 ) − n C(n,2)-n C(n,2)−n条边方向可以随意,有 2 C ( n , 2 ) − n 2^{C(n,2)-n} 2C(n,2)−n种方案
- 值得记录的竞赛图就是 n n n个点强连通的竞赛图
- 设 f [ i ] f[i] f[i]表示 i i i个点强连通的竞赛图的数量
- 考虑用总竞赛图数减去不强连通的竞赛图的数量
- 总竞赛图数: 2 C ( i , 2 ) 2^{C(i,2)} 2C(i,2)
- 枚举不强连通的竞赛图中,拓扑序最小的强连通分量的大小
- 大小为 j j j的图数: C ( i , j ) ∗ f [ j ] ∗ 2 C ( i − j , 2 ) C(i,j)*f[j]*2^{C(i-j,2)} C(i,j)∗f[j]∗2C(i−j,2)
- 即从 i i i个点中选出 j j j个点放入拓扑序最小的强连通分量,这 i i i个点的强连通竞赛图数量为 f [ j ] f[j] f[j],此时这 j j j个点相互之间的连边已经确定
- 由于拓扑序最小,那么这 j j j个点和另外 i − j i-j i−j个点之间的连边,必定是以这 j j j个点为起点
- 那么还剩 C ( i − j , 2 ) C(i-j,2) C(i−j,2)条边,即另外 i − j i-j i−j个点相互之间的连边方向随意
- 由此可得 d p dp dp式: f [ i ] = 2 C ( i , 2 ) − ∑ j = 1 i − 1 C ( i , j ) ∗ f [ j ] ∗ 2 C ( i − j , 2 ) f[i]=2^{C(i,2)}-\sum_{j=1}^{i-1}C(i,j)*f[j]*2^{C(i-j,2)} f[i]=2C(i,2)−∑j=1i−1C(i,j)∗f[j]∗2C(i−j,2)
- 设 g [ i ] = 2 C ( i , 2 ) g[i] =2^{C(i,2)} g[i]=2C(i,2)
- 那么 f [ i ] = g [ i ] − ∑ j = 1 i − 1 C ( i , j ) ∗ f [ j ] ∗ g [ i − j ] f[i]=g[i]-\sum_{j=1}^{i-1}C(i,j)*f[j]*g[i-j] f[i]=g[i]−∑j=1i−1C(i,j)∗f[j]∗g[i−j]
- 再把 C C C拆成阶乘形式
- f [ i ] = g [ i ] − ∑ j = 1 i − 1 i ! ∗ f [ j ] ÷ j ! ∗ g [ i − j ] ÷ ( i − j ) ! f[i]=g[i]-\sum_{j=1}^{i-1}i!*f[j]\div j!*g[i-j]\div (i-j)! f[i]=g[i]−∑j=1i−1i!∗f[j]÷j!∗g[i−j]÷(i−j)!
- 两边同除以 i ! i! i!,得
- f [ i ] ÷ i ! = g [ i ] ÷ i ! − ∑ j = 1 i − 1 f [ j ] ÷ j ! ∗ g [ i − j ] ÷ ( i − j ) ! f[i]\div i!=g[i]\div i!-\sum_{j=1}^{i-1}f[j]\div j!*g[i-j]\div (i-j)! f[i]÷i!=g[i]÷i!−∑j=1i−1f[j]÷j!∗g[i−j]÷(i−j)!
- 记 A [ i ] = f [ i ] / i ! , B [ i ] = g [ i ] / i ! A[i]=f[i]/i!,B[i]=g[i]/i! A[i]=f[i]/i!,B[i]=g[i]/i!
- 那么 A [ i ] = B [ i ] − ∑ j = 1 i − 1 A [ j ] ∗ B [ i − j ] A[i]=B[i]-\sum_{j=1}^{i-1}A[j]*B[i-j] A[i]=B[i]−∑j=1i−1A[j]∗B[i−j]
- 令 B [ 0 ] = 1 , A [ 0 ] = 0 B[0]=1,A[0]=0 B[0]=1,A[0]=0,那么上式可以化为 B [ i ] = ∑ j = 0 i A [ j ] ∗ B [ i − j ] B[i]=\sum_{j=0}^{i}A[j]*B[i-j] B[i]=∑j=0iA[j]∗B[i−j]
- 发现 A ∗ B A*B A∗B就是 B − B [ 0 ] B-B[0] B−B[0],因为 B [ 0 ] ≠ A [ 0 ] ∗ B [ 0 ] B[0]≠A[0]*B[0] B[0]̸=A[0]∗B[0],但对于 i > 0 i>0 i>0的情况,上式均成立
- 于是 A ∗ B = B − 1 A*B=B-1 A∗B=B−1,化简得 1 − A = B − 1 1-A=B^{-1} 1−A=B−1
- 对多项式B求逆即可
【参考程序】
#include <bits/stdc++.h>
using namespace std;
#define ll long long
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);
}
const int e = 4e5 + 5, mod = 998244353;
int a[e], b[e], c[e], d[e], n, rev[e], lim = 1, m, f[e], g[e], fac[e], inv[e], p[e];
inline void upt(int &x, int y)
{
x = y;
if (x >= mod) x -= mod;
}
inline int ksm(int x, ll y)
{
int res = 1;
while (y)
{
if (y & 1) res = (ll)res * x % mod;
y >>= 1;
x = (ll)x * x % mod;
}
return res;
}
inline void fft(int n, int *a, int opt)
{
int i, j, k, r = (opt == 1 ? 3 : (mod + 1) / 3);
for (i = 0; i < n; i++)
if (i < rev[i]) swap(a[i], a[rev[i]]);
for (k = 1; k < n; k <<= 1)
{
int w0 = ksm(r, (mod - 1) / (k << 1));
for (i = 0; i < n; i += (k << 1))
{
int w = 1;
for (j = 0; j < k; j++)
{
int b = a[i + j], c = (ll)w * a[i + j + k] % mod;
upt(a[i + j], b + c);
upt(a[i + j + k], b + mod - c);
w = (ll)w * w0 % mod;
}
}
}
}
int main()
{
read(n);
int i, j, k = 0;
fac[0] = 1;
for (i = 1; i <= n; i++) fac[i] = (ll)fac[i - 1] * i % mod;
inv[n] = ksm(fac[n], mod - 2);
for (i = n - 1; i >= 0; i--) inv[i] = (ll)inv[i + 1] * (i + 1) % mod;
for (i = 1; i <= n; i++)
{
g[i] = ksm(2, (ll)i * (i - 1) / 2);
a[i] = (ll)g[i] * inv[i] % mod;
}
a[0] = 1;
b[0] = ksm(a[0], mod - 2);
for (m = 1; m < (n + 1) * 2; m <<= 1)
{
while (lim < m * 2)
{
lim <<= 1;
k++;
}
for (i = 0; i < lim; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << k - 1);
for (i = 0; i < lim; i++)
if (i < lim / 2)
{
c[i] = a[i];
d[i] = b[i];
}
else c[i] = d[i] = 0;
fft(lim, c, 1);
fft(lim, b, 1);
for (i = 0; i < lim; i++) c[i] = (ll)c[i] * b[i] % mod * b[i] % mod;
fft(lim, c, -1);
int tot = ksm(lim, mod - 2);
for (i = 0; i < lim; i++) c[i] = (ll)c[i] * tot % mod;
for (i = 0; i < lim; i++)
if (i < lim / 2) b[i] = (2ll * d[i] + mod - c[i]) % mod;
else b[i] = 2ll * d[i] % mod;
}
b[0] = mod + 1 - b[0];
for (i = 1; i <= n; i++) b[i] = mod - b[i];
for (i = 0; i <= n; i++) b[i] = (ll)b[i] * fac[i] % mod;
for (i = 1; i <= n; i++)
{
if (!b[i])
{
puts("-1");
continue;
}
if (i == 1)
{
puts("1");
continue;
}
int cnt = (ll)ksm(2, (ll)i * (i - 1) / 2 - i) * fac[i - 1] % mod;
printf("%d\n", (ll)cnt * ksm(b[i], mod - 2) % mod);
}
return 0;
}