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

射命丸文的笔记

题目链接:luogu P4233

题目大意

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

思路

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

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

那接着问题就是后者,考虑一个容斥 DP:
\(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}}\)
这里稍微解释一下,你每次就枚举一个块和其他的部分不能连通,那其他的部分是要连通的所以是 \(f_i\)(这里枚举的是其他的部分),然后这个块里面随便选所以是 \(2^{\frac{(n-i)(n-i-1)}{2}}\),然后它们这两个部分之间肯定是只有从一边连向另一边,不用乘二是因为乘二到时就会有重复计算。

然后你考虑优化:
发现在 \(i=n\) 的时候 \(\binom{n}{i}2^{\frac{(n-i)(n-i-1)}{2}}\) 这个东西是 \(1\),那我们完全可以把右边的减移到左边,从而变成这个:
\(\sum\limits_{i=1}^{n}f_i\binom{n}{i}2^{\frac{(n-i)(n-i-1)}{2}}=2^{\frac{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}}\)
移项:
\(\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!}\)

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

代码

#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;
}

posted @ 2022-03-03 16:17  あおいSakura  阅读(35)  评论(0编辑  收藏  举报