HAOI2018 染色

設 G[k] 表示恰好出現 S 次的顏色恰好有 k 種, 數值爲方案總數。設 n = min(M, floor(N/S)), 這道題的答案就是 Σ1≤i≤n Wi × G[i]。

設 F[k] 表示恰好出現 S 次的顏色至少有 k 種, 數值爲方案總數。這個很好算, 先選出顏色 \(\dbinom Mk\), 然後把沒選出的顏色當成無色, 按照可重排列的方式構造出所有局面 \(\dfrac{N!}{(S!)^k(N-S*k)!}\), 最後把所有無色填上顏色 \((M-k)^{N-S*k}\)

很顯然的,

\[F[k] = \sum_{i = k}^n \binom ik * G[i] \]

爲什麼會有二項式係數呢?因爲同一個方案會以這些數量的 “F特有的” 方式被觀測到。

現在二項式反演, 得到:

\[G[k] = \sum_{i = k}^n (-1)^{n - k}\binom ik F[i] \]

現在得到了一個 O(n2) 的算法。


拆解一下:

\[G[k] = \sum_{i=k}^n (-1)^{i-k}\frac{i!}{k!(i-k)!}F[i] \\ G[k] * k! = \sum_{i=k}^n \left(\frac{(-1)^{i-k}}{(i-k)!}\right)*(F[i]*i!) \]

\(A[i] = \dfrac{(-1)^i}{i!}\)\(B[i] = F[i] * i!\), 那麼

\[G[k] * k! = \sum_{i = k}^n A[i - k] * B[i] \]

把 B 翻轉一下變成 \(\hat B[n - i] = B[i]\), 就有:

\[G[k] * k! = \sum_{i = k}^n A[i-k] * \hat B[n - i] \\ G[k] * k! = \sum_{i = 0}^{n-k} A[i] * \hat B[n - k - i] \]

這是 \((A * \hat B)[n - k]\)

本題做法就清晰明瞭了。


對於模數, 首先, 測試的結果是模數是質數, 模數減一的質因數分解是 221 × 479。

原根暫時不會, 就拿題解的原根來用把(

#include<bits/stdc++.h>
typedef long long LL;
using namespace std;

#define int long long

const int maxn = 4e5 + 23, mo = 1004535809;
int ksm(int a, int b) {
	int res = 1;
	for(; b; b = b>>1, a = ((LL)a * a) % mo)
		if(b & 1) res = ((LL)res * a) % mo;
	return res;
}
const int g = 3, ig = ksm(g, mo - 2);

int N, M, S;
int fac[10000003], ifac[10000003];
int C(int n, int m) {
	return m > n ? 0 : (LL)fac[n] * (LL)ifac[m] % mo * (LL)ifac[n - m] % mo;
}
int calcF(int i) {
	return (LL)C(M, i) * fac[N] % mo * ksm(ifac[S], i) % mo
						* ifac[N - S * i] % mo * ksm(M - i, N - S * i) % mo;
}

int rv[maxn];
void NTT(int *a, int n, int type) {
	for(int i = 0; i < n; ++i) if(i < rv[i]) swap(a[i], a[rv[i]]);
	for(int m = 2; m <= n; m = m << 1) {
		int w = ksm(type == 1 ? g : ig, (mo - 1) / m);
		for(int i = 0; i < n; i += m) {
			int tmp = 1;
			for(int j = 0; j < (m >> 1); ++j) {
				int p = a[i + j], q = (LL)tmp * a[i + j + (m >> 1)];
				a[i + j] = (p + q) % mo, a[i + j + (m >> 1)] = (p - q + mo) % mo;
				tmp = (LL)tmp * w % mo;
			}
		}
	}
	if(type == -1) {
		int Inv = ksm(n, mo - 2);
		for(int i = 0; i < n; ++i) a[i] = (LL)a[i] * Inv % mo;
	}
}

int n, W[maxn];
int A[maxn], B[maxn], G[maxn];

signed main()
{
	scanf("%lld%lld%lld", &N, &M, &S);
	for(int i = 0; i <= M; ++i) scanf("%lld", &W[i]);
	n = min(M, N / S);
	int D = max(N, max(M, S));
	fac[0] = 1;
	for(int i = 1; i <= D; ++i) fac[i] = (LL)i * (LL)fac[i - 1] % mo;
	ifac[D] = ksm(fac[D], mo - 2);
	for(int i = D; i > 0; --i) ifac[i - 1] = (LL)ifac[i] * (LL)i % mo;
	
	for(int i = 0; i <= n; ++i) {
		A[i] = ifac[i]; if(i & 1) A[i] = (mo - A[i]);
		B[i] = (LL)fac[i] * (LL)calcF(i) % mo;
	}
	reverse(B, B + n + 1);
	
	int len = 1; while(len < ((n+1) << 1)) len = len << 1;
	for(int i = 1; i < len; ++i) rv[i] = (rv[i>>1]>>1) | ((i&1) ? len>>1 : 0);
	NTT(A, len, 1), NTT(B, len, 1);
	for(int i = 0; i < len; ++i) G[i] = (LL)A[i] * (LL)B[i] % mo;
	NTT(G, len, -1);
	reverse(G, G + n + 1);
	for(int i = 0; i <= n; ++i) G[i] = (LL)G[i] * ifac[i] % mo;
	long long ans = 0ll;
	for(int i = 0; i <= n; ++i) ans = ans + ((LL)G[i] * (LL)W[i] % mo), ans = ans % mo;
	cout << (ans % mo + mo) % mo;
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章