【luogu P4233】射命丸文的笔记(NTT)(多项式求逆)

射命丸文的笔记

题目链接:luogu P4233

题目大意

问你有每个 n 个点的有哈密顿回路的竞赛图的期望哈密顿回路数。
其中哈密顿回路就是从一个点出发不重复的经过每个点最终回到自己的路径。
竞赛图就是任意两点之间都有一条有向边。

思路

考虑求出所有图的哈密顿回路总数和有多少个图有哈密顿回路。

前者比较好求,你就选一套路径 n ! n = ( n − 1 ) ! \dfrac{n!}{n}=(n-1)! nn!=(n1)!,然后其它边就可以随意了 2 ( n ( n − 1 ) 2 − n ) 2^{(\frac{n(n-1)}{2}-n)} 2(2n(n1)n)

那接着问题就是后者,考虑一个容斥 DP:
f n = 2 n ( n − 1 ) 2 − ∑ i = 1 n − 1 f i ( n i ) 2 ( n − i ) ( n − i − 1 ) 2 f_n=2^{\frac{n(n-1)}{2}}-\sum\limits_{i=1}^{n-1}f_i\binom{n}{i}2^{\frac{(n-i)(n-i-1)}{2}} fn=22n(n1)i=1n1fi(in)22(ni)(ni1)
这里稍微解释一下,你每次就枚举一个块和其他的部分不能连通,那其他的部分是要连通的所以是 f i f_i fi(这里枚举的是其他的部分),然后这个块里面随便选所以是 2 ( n − i ) ( n − i − 1 ) 2 2^{\frac{(n-i)(n-i-1)}{2}} 22(ni)(ni1),然后它们这两个部分之间肯定是只有从一边连向另一边,不用乘二是因为乘二到时就会有重复计算。

然后你考虑优化:
发现在 i = n i=n i=n 的时候 ( n i ) 2 ( n − i ) ( n − i − 1 ) 2 \binom{n}{i}2^{\frac{(n-i)(n-i-1)}{2}} (in)22(ni)(ni1) 这个东西是 1 1 1,那我们完全可以把右边的减移到左边,从而变成这个:
∑ i = 1 n f i ( n i ) 2 ( n − i ) ( n − i − 1 ) 2 = 2 n ( n − 1 ) 2 \sum\limits_{i=1}^{n}f_i\binom{n}{i}2^{\frac{(n-i)(n-i-1)}{2}}=2^{\frac{n(n-1)}{2}} i=1nfi(in)22(ni)(ni1)=22n(n1)
然后我们拆开组合数:
∑ i = 1 n f i n ! i ! ( n − i ) ! 2 ( n − i ) ( n − i − 1 ) 2 = 2 n ( n − 1 ) 2 \sum\limits_{i=1}^{n}f_i\dfrac{n!}{i!(n-i)!}2^{\frac{(n-i)(n-i-1)}{2}}=2^{\frac{n(n-1)}{2}} i=1nfii!(ni)!n!22(ni)(ni1)=22n(n1)
移项:
∑ i = 1 n f i i ! 2 ( n − i ) ( n − i − 1 ) 2 ( n − i ) ! = 2 n ( n − 1 ) 2 n ! \sum\limits_{i=1}^{n}\dfrac{f_i}{i!}\dfrac{2^{\frac{(n-i)(n-i-1)}{2}}}{(n-i)!}=\dfrac{2^{\frac{n(n-1)}{2}}}{n!} i=1ni!fi(ni)!22(ni)(ni1)=n!22n(n1)

然后发现我们设 F = ∑ i f i i ! x i , G = ∑ i 2 i ( i − 1 ) 2 i ! x i F=\sum\limits_{i}\dfrac{f_i}{i!}x^i,G=\sum\limits_{i}\dfrac{2^{\frac{i(i-1)}{2}}}{i!}x^i F=ii!fixi,G=ii!22i(i1)xi,然后就变成了这个:
G ( x ) = F ( x ) G ( x ) + 1 G(x)=F(x)G(x)+1 G(x)=F(x)G(x)+1(这个 + 1 +1 +1 是因为上面的 1 ∼ n 1\sim n 1n 的时候是没有第 0 0 0 项,要加回来)
然后就有 F ( x ) = G ( x ) − 1 G ( x ) F(x)=\dfrac{G(x)-1}{G(x)} F(x)=G(x)G(x)1,然后用多项式求逆即可。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
#define mo 998244353

#define clear(f, n) memset(f, 0, (n) * sizeof(ll))
#define cpy(f, g, n) memcpy(f, g, (n) * sizeof(ll))

using namespace std;

int n, an[800001], limit, l_size;
ll f[800001], G, Gv, g[800001];
ll w[800001], r[800001], tmp[800001];
ll jc[800001], inv[800001];

ll ksm(ll x, ll y) {
	ll re = 1;
	while (y) {
		if (y & 1) re = re * x % mo;
		x = x * x % mo;
		y >>= 1;
	}
	return re;
}

void get_an() {
	for (int i = 0; i < limit; i++)
		an[i] = (an[i >> 1] >> 1) | ((i & 1) << (l_size - 1));
}

void NTT(ll *f, ll op) {
	get_an();
	for (int i = 0; i < limit; i++)
		if (i < an[i]) swap(f[i], f[an[i]]);
	
	for (int mid = 1; mid < limit; mid <<= 1) {
		ll Wn = ksm(op == 1 ? G : Gv, (mo - 1) / (mid << 1));
		for (int R = (mid << 1), j = 0; j < limit; j += R) {
			ll w = 1;
			for (int k = 0; k < mid; k++, w = w * Wn % mo) {
				ll x = f[j + k], y = w * f[j + mid + k] % mo;
				f[j + k] = (x + y) % mo;
				f[j + mid + k] = (x - y + mo) % mo;
			}
		}
	}
	
	if (op == -1) {
		ll liv = ksm(limit, mo - 2);
		for (int i = 0; i < limit; i++)
			f[i] = f[i] * liv % mo;
	}
}

void px(ll *x, ll *y) {
	for (int i = 0; i < limit; i++)
		x[i] = x[i] * y[i] % mo;
}

void cheng(ll *x, int n, ll *y, int m) {
	limit = 1; l_size = 0;
	while (limit < n + m + 1) {
		limit <<= 1; l_size++;
	}
	NTT(x, 1); NTT(y, 1);
	px(x, y); NTT(x, -1);
}

void invp(ll *F, int n) {
	w[0] = ksm(F[0], mo - 2);
	l_size = 0;
	for (int len = 2; (len >> 1) <= n; len <<= 1) {
		limit = len; l_size++;
		for (int i = 0; i < (len >> 1); i++) r[i] = w[i];
		cpy(tmp, F, len);
		NTT(tmp, 1); NTT(r, 1);
		px(r, tmp); NTT(r, -1);
		clear(r, (len >> 1));
		cpy(tmp, w, len);
		NTT(tmp, 1); NTT(r, 1);
		px(r, tmp); NTT(r, -1);
		
		for (int i = len >> 1; i < len; i++)
			w[i] = (w[i] * 2 - r[i] + mo) % mo;
	}
	cpy(F, w, n);
	clear(tmp, n); clear(w, n); clear(r, n);
}

int main() {
	G = 3; Gv = ksm(G, mo - 2);
	
	scanf("%d", &n);
	jc[0] = 1; for (int i = 1; i <= n; i++) jc[i] = jc[i - 1] * i % mo;
	inv[0] = inv[1] = 1; for (int i = 2; i <= n; i++) inv[i] = inv[mo % i] * (mo - mo / i) % mo;
	for (int i = 1; i <= n; i++) inv[i] = inv[i - 1] * inv[i] % mo;
	for (int i = 0; i <= n; i++) {
		f[i] = ksm(2, 1ll * i * (i - 1) / 2) * inv[i] % mo;
	}
	
	cpy(g, f, n + 1); g[0] = (g[0] - 1 + mo) % mo;
	invp(f, n + 1);
	cheng(f, n + 1, g, n + 1);
	
	for (int i = 1; i <= n; i++) {
		if (i == 1) {
			printf("1\n"); continue;
		}
		if (i == 2) {
			printf("-1\n"); continue;
		}
		f[i] = f[i] * jc[i] % mo;
		ll all = ksm(2, 1ll * i * (i - 1) / 2 - i) * jc[i - 1] % mo;
		printf("%lld\n", all * ksm(f[i], mo - 2) % mo);
	}
	
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值