Description
- 如果一个竞赛图含有哈密顿回路,则称这张竞赛图为值得记录的
- 从所有含有\(n\)个顶点(顶点互不相同)的,值得记录的竞赛图中等概率随机选取一个
- 求选取的竞赛图中哈密顿回路数量的期望
- 输出答案除以\(998244353\)的余数
- 竞赛图:指任意两个顶点间恰有一条有向边的有向图
- 哈密顿回路:指除起点和终点外经过所有顶点恰好一次且起点和终点相同的路径
Solution
- 组合数学 \(+\) 多项式求逆 \(+\) \(dp\)
- 哈密顿回路数量的期望 \(=\) 所有竞赛图的哈密顿回路总数 \(/\) 值得记录的竞赛图个数
- 显然哈密顿回路总数 \(=\) \(2^{C(n,2)-n}*(n-1)!\)
- 即考虑\(n\)个点的\(n-1\)种圆排列,每个点往排列中的下一个点连边,排列中的最后一个点往第一个点连边
- 总共有\(C(n,2)\)条边,确定一个圆排列,就确定了\(n\)条边的方向,剩下\(C(n,2)-n\)条边方向可以随意,有\(2^{C(n,2)-n}\)种方案
- 值得记录的竞赛图就是\(n\)个点强连通的竞赛图
- 设\(f[i]\)表示\(i\)个点强连通的竞赛图的数量
- 考虑用总竞赛图数减去不强连通的竞赛图的数量
- 总竞赛图数:\(2^{C(i,2)}\)
- 枚举不强连通的竞赛图中,拓扑序最小的强连通分量的大小
- 大小为\(j\)的图数:\(C(i,j)*f[j]*2^{C(i-j,2)}\)
- 即从\(i\)个点中选出\(j\)个点放入拓扑序最小的强连通分量,这\(i\)个点的强连通竞赛图数量为\(f[j]\),此时这\(j\)个点相互之间的连边已经确定
- 由于拓扑序最小,那么这\(j\)个点和另外\(i-j\)个点之间的连边,必定是以这\(j\)个点为起点
- 那么还剩\(C(i-j,2)\)条边,即另外\(i-j\)个点相互之间的连边方向随意
- 由此可得\(dp\)式:\(f[i]=2^{C(i,2)}-\sum_{j=1}^{i-1}C(i,j)*f[j]*2^{C(i-j,2)}\)
- 设\(g[i] =2^{C(i,2)}\)
- 那么\(f[i]=g[i]-\sum_{j=1}^{i-1}C(i,j)*f[j]*g[i-j]\)
- 再把\(C\)拆成阶乘形式
- \(f[i]=g[i]-\sum_{j=1}^{i-1}i!*f[j]\div j!*g[i-j]\div (i-j)!\)
- 两边同除以\(i!\),得
- \(f[i]\div i!=g[i]\div i!-\sum_{j=1}^{i-1}f[j]\div j!*g[i-j]\div (i-j)!\)
- 记\(A[i]=f[i]/i!,B[i]=g[i]/i!\)
- 那么\(A[i]=B[i]-\sum_{j=1}^{i-1}A[j]*B[i-j]\)
- 令\(B[0]=1,A[0]=0\),那么上式可以化为\(B[i]=\sum_{j=0}^{i}A[j]*B[i-j]\)
- 发现\(A*B\)就是\(B-B[0]\),因为\(B[0]≠A[0]*B[0]\),但对于\(i>0\)的情况,上式均成立
- 于是\(A*B=B-1\),化简得\(1-A=B^{-1}\)
- 对多项式B求逆即可
Code
#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;
}