• [洛谷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;
    }
    
  • 相关阅读:
    任务Task系列之Parallel的静态For,ForEach,Invoke方法
    任务Task系列之使用CancellationToken取消Task
    泛型基础
    串的两种模式匹配算法
    数据结构之串类型
    c#基础知识之设计类型
    挣脱
    数据结构之栈和队列
    数据结构之线性表
    NGUI背包系统
  • 原文地址:https://www.cnblogs.com/cyf32768/p/12196321.html
Copyright © 2020-2023  润新知