Live2D

Solution -「UOJ #450」复读机

\(\mathcal{Description}\)

  Link.

  求从 \(m\) 种颜色,每种颜色无限多的小球里选 \(n\) 个构成排列,使得每种颜色出现次数为 \(d\) 的倍数的排列方案数,对 \(19491001\) 取模。

  \(n\le10^9\)

  • \(m\le10^3\)\(d=3\)

  • \(m\le5\times10^5\)\(d\le2\)

\(\mathcal{Solution}\)

  分 \(d=1,2,3\) 求解。

  当 \(d=1\),每个位置 \(m\) 种方案,答案为 \(m^n\)

  当 \(d=2\),偶数序列的 EGF 为 \(G(x)=\sum_{i=0}^{+\infty}\frac{x^{2i}}{(2i)!}=\frac{e^x+e^{-x}}2\),那么答案为:

\[\begin{aligned} n![x^n]G^m(x)&=n![x^n]\left( \frac{e^x+e^{-x}}2 \right)^m\\ &=\frac{n!}{2^m}[x^n]\left( \sum_{i=0}^m\binom{m}ie^{(2i-m)x} \right)\\ &=2^{-m}\sum_{i=0}^m\binom{m}i(2i-m)^n \end{aligned} \]

  第二步到第三步用到常见的 \(e^{ax}=\sum_{i=0}^{+\infty}\frac{a^i}{i!}x^i\)。此时就能 \(\mathcal O(m\log n)\) 求出答案了。

  当 \(d=3\)\(3\) 的倍数数的 EGF 为 \(G(x)=\sum_{i=0}^{+\infty}[3|i]\frac{x^i}{i!}\),这个不太好算,来一发单位根反演:

\[\begin{aligned} G(x)&=\sum_{i=0}^{+\infty}[3|i]\frac{x^i}{i!}\\ &=\frac{1}3\sum_{i=0}^{+\infty}\frac{x^i}{i!}\sum_{j=0}^2\omega_3^{ij}\\ &=\frac{1}3\sum_{j=0}^2\sum_{i=0}^{+\infty}\frac{(\omega_3^jx)^i}{i!}\\ &=\frac{1}3\sum_{j=0}^2e^{\omega_3^jx} \end{aligned} \]

  接着求答案,暴力展开三项式幂:

\[\begin{aligned} n![x^n]G^m(x)&=\frac{n!}{3^m}[x^n]\left( \sum_{j=0}^2e^{\omega_3^jx} \right)^m\\ &=\frac{n!}{3^m}[x^n]\left( \sum_{a+b+c=m}\binom{m}{a,b,c}e^{(a\omega_3^0+b\omega_3^1+c\omega_3^2)x} \right)\\ &=3^{-m}\sum_{a+b+c=m}\binom{m}{a,b,c}(a\omega_3^0+b\omega_3^1+c\omega_3^2)^n \end{aligned} \]

  注意到 \(c=m-a-b\),所以多重组合数的值就是 \(\frac{m!}{a!b!c!}\),该式能在 \(\mathcal O(m^2\log n)\) 的时间内算出。实际上该式就是 \(d=2\) 的情况的扩展,由于 \(\omega_2^{0,1}=\pm1\),所以亦能从该式推回 \(d=2\) 的情况。

\(\mathcal{Code}\)

/* Clearink */

#include <cstdio>

#define rep( i, l, r ) for ( int i = l, rpbound##i = r; i <= rpbound##i; ++i )
#define per( i, r, l ) for ( int i = r, rpbound##i = l; i >= rpbound##i; --i )

const int MOD = 19491001, MAXM = 5e5, INV2 = MOD + 1 >> 1, INV3 = 12994001;
const int W[] = { 1, 663067, 18827933 };
int n, m, d, fac[MAXM + 5], ifac[MAXM + 5];

inline int mul ( long long a, const int b ) { return a * b % MOD; }
inline int sub ( int a, const int b ) { return ( a -= b ) < 0 ? a + MOD : a; }
inline int add ( int a, const int b ) { return ( a += b ) < MOD ? a : a - MOD; }
inline int mpow ( int a, int b ) {
	int ret = 1;
	for ( ; b; a = mul ( a, a ), b >>= 1 ) ret = mul ( ret, b & 1 ? a : 1 );
	return ret;
}

inline void init () {
	fac[0] = 1;
	rep ( i, 1, m ) fac[i] = mul ( i, fac[i - 1] );
	ifac[m] = mpow ( fac[m], MOD - 2 );
	per ( i, m - 1, 0 ) ifac[i] = mul ( i + 1, ifac[i + 1] );
}

inline int comb ( const int n, const int m ) {
	return n < m ? 0 : mul ( fac[n], mul ( ifac[m], ifac[n - m] ) );
}

int main () {
	scanf ( "%d %d %d", &n, &m, &d ), init ();
	if ( d == 1 ) return printf ( "%d\n", mpow ( m, n ) ), 0;
	if ( d == 2 ) {
		int ans = 0;
		rep ( i, 0, m ) {
			ans = add ( ans, mul ( comb ( m, i ), mpow ( sub ( i << 1, m ), n ) ) );
		}
		printf ( "%d\n", mul ( ans, mpow ( INV2, m ) ) );
		return 0;
	}
	// $d is now smaller than 1000.
	int ans = 0;
	rep ( i, 0, m ) rep ( j, 0, m - i ) {
		int k = m - i - j;
		ans = add ( ans, mul ( mul ( ifac[i], mul ( ifac[j], ifac[k] ) ),
			mpow ( add (
				mul ( i, W[0] ), add ( mul ( j, W[1] ), mul ( k, W[2] ) ) ), n ) ) );
	}
	printf ( "%d\n", mul ( ans, mul ( fac[m], mpow ( INV3, m ) ) ) );
	return 0;
}
posted @ 2021-01-06 13:54  Rainybunny  阅读(52)  评论(0编辑  收藏  举报