[洛谷P4233]射命丸文的笔记

【题目大意】

  • 如果一个竞赛图含有哈密顿回路,则称这张竞赛图为值得记录的
  • 从所有含有 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(n1)!
  • 即考虑 n n n个点的 n − 1 n-1 n1种圆排列,每个点往排列中的下一个点连边,排列中的最后一个点往第一个点连边
  • 总共有 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(ij,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 ij个点之间的连边,必定是以这 j j j个点为起点
  • 那么还剩 C ( i − j , 2 ) C(i-j,2) C(ij,2)条边,即另外 i − j i-j ij个点相互之间的连边方向随意
  • 由此可得 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=1i1C(i,j)f[j]2C(ij,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=1i1C(i,j)f[j]g[ij]
  • 再把 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=1i1i!f[j]÷j!g[ij]÷(ij)!
  • 两边同除以 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=1i1f[j]÷j!g[ij]÷(ij)!
  • 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=1i1A[j]B[ij]
  • B [ 0 ] = 1 , A [ 0 ] = 0 B[0]=1,A[0]=0 B[0]=1A[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[ij]
  • 发现 A ∗ B A*B AB就是 B − B [ 0 ] B-B[0] BB[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 AB=B1,化简得 1 − A = B − 1 1-A=B^{-1} 1A=B1
  • 对多项式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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值