1. 程式人生 > 其它 >【luogu P4233】射命丸文的筆記(NTT)(多項式求逆)

【luogu P4233】射命丸文的筆記(NTT)(多項式求逆)

問你有每個 n 個點的有哈密頓迴路的競賽圖的期望哈密頓迴路數。 其中哈密頓迴路就是從一個點出發不重複的經過每個點最終回到自己的路徑。 競賽圖就是任意兩點之間都有一條有向邊。

射命丸文的筆記

題目連結: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;
}