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;
}
上一篇:concatenate of numpy


下一篇:Numpy学习笔记(五):np.concatenate函数和np.append函数用于数组拼接