[洛谷P4233]射命丸文的笔记(多项式求逆)

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;
}
posted @ 2020-01-15 13:59  花淇淋  阅读(119)  评论(0编辑  收藏  举报