题目

点这里看题目。

分析

介绍两种做法,一种是简单的题解方法,另一种是复杂的做法。

题解

假设有 \(C\) 种不同的颜色,我们可以先将颜色离散化,标号为 \(1,2,\dots,C\)。设第 \(i\) 种颜色数量为 \(a_i\)

“不同的颜色的数量”也就是出现了的颜色的种数。因此我们可以设离散随机变量 \(X_i\) 表示第 \(i\) 种颜色是否出现,如果 \(X_i=1\) 则第 \(i\) 种颜色出现,否则 \(X_i=0\),表示第 \(i\) 种颜色不出现。

我们需要求的是 \(E[\sum_{i=1}^CX_i]=\sum_{i=1}^CE[X_i]\),而由于 \(X_i\) 的取值只有 \(\{0,1\}\),所以 \(E[X_i]=P(X_i=1)\)。我们也就是要求第 \(i\) 种颜色出现的概率,当我们枚举选择的糖果数量 \(k\) 时,使用容斥原理得到概率为:

\[\frac{\binom{n}{k}-\binom{n-a_i}{k}}{\binom{n}{k}} \]

\(n,k\) 确定时,这个式子只和 \(a_i\) 相关。由于 \(\sum_{i=1}^Ca_i=n\),所以不同的 \(a_i\) 只有 $ O(\sqrt n)$ 种。对于不同的 \(a_i\) 单独计算,乘上系数即可。复杂度为 \(O(n\sqrt n)\)

复杂做法

显然将颜色排序不会影响结果,我们将输入的 \(c\) 序列排序,那么对于选出来的 \(k\) 个糖果,只有可能在相邻的两项之间产生贡献。根据期望的线性性,我们可以枚举相邻的两项,计算它们可能带来的贡献。

枚举 \(k,k>1\),可以计算所有的贡献为:

\[\binom{n}{k}+\sum_{i=1}^{n-1}\sum_{j=i+1}^n[c_i\not=c_j]\binom{n-j+i-1}{k-2} \]

期望就还需要除掉 \(\binom{n}{k}\) 的情况总数,下面仅对这个式子化简。

\([c_i\not=c_j]\) 不好搞,把它拆成 \(1-[c_i=c_j]\)。我们先处理前面一半:

\[\begin{aligned} \sum_{i=1}^{n-1}\sum_{j=i+1}^n\binom{n-j+i-1}{k-2} &=\sum_{i=1}^{n-1}\binom{n-1-i}{k-2}(n-i)\\ &=\sum_{i=1}^{n-1}\binom{n-i}{k-1}(k-1)\\ &=(k-1)\left(\binom{n}{k}-\binom{0}{k-1}\right)\\ &=(k-1)\binom{n}{k} \end{aligned} \]

接着考虑 \([c_i=c_j]\)。假设 \(c_i\) 出现次数为 \(q\),这就相当于要求 \(1\le j-i\le q\),所以:

\[\begin{aligned} \sum_{j=1}^{q-1}\binom{n-1-j}{k-2}(q-j) &=\sum_{j=1}^{q-1}\sum_{i=0}^{k-2}\binom{n-q}{k-2-i}\binom{q-1-j}{i}(q-j)\\ &=\sum_{i=0}^{k-2}\binom{n-q}{k-2-i}\sum_{j=1}^{q-1}\binom{q-j}{i+1}(i+1)\\ &=\sum_{i=0}^{k-2}\binom{n-q}{k-2-i}\binom{q}{i+2}(i+1)\\ &=\sum_{i=1}^{k}\binom{n-q}{i}\binom{q}{i+2}(i-1) \end{aligned} \]

现在开始,式子看起来不太友好,我们改用生成函数。尝试构造 \(\sum_{i\ge 1}\binom{q}{i}(i-1)x^i\),从系数入手:

\[\begin{aligned} \sum_{i\ge 1}\binom{q}{i}(i-1)x^i &=\left(\frac{(1+x)^q-1}{x}\right)'x^2\\ &=q(1+x)^{q-1}x-(1+x)^q+1 \end{aligned} \]

对于系数的构造恰好使得我们得到的生成函数没有常数项。所以和式正好是卷积的形式,我们需要的答案为:

\[[x^k]q(1+x)^{n-1}x-(1+x)^n+(1+x)^{n-q} \]

同样的道理,不同的 \(q\) 只有 \(O(\sqrt{n})\) 种,所以对于不同的 \(q\) 计算一遍即可。复杂度为 \(O(n\sqrt{n})\)

小结:

  1. 比较一下两种做法,第一种做法简单的原因在于它将每种颜色单独考虑,而不像第二种是考虑一对糖果,所以才能大大简化运算;当然,第二种方法其实也用了这种思想。
  2. 推导生成函数的时候,以系数为突破口,常用的方法之一就是根据已有结果构造出需要的系数。
  3. 再次提醒用到的 \(\sqrt n\) 性质
代码

这里只给出第二种做法的代码:

#include <cstdio>
#include <vector>
#include <utility>
#include <algorithm>
 
#define rep( i, a, b ) for( int i = (a) ; i <= (b) ; i ++ )
#define per( i, a, b ) for( int i = (a) ; i >= (b) ; i -- )
 
const int mod = 998244353;
const int MAXN = 1e5 + 5;
 
template<typename _T>
void read( _T &x )/*{{{*/
{
	x = 0; char s = getchar(); int f = 1;
	while( ! ( '0' <= s && s <= '9' ) ) { f = 1; if( s == '-' ) f = -1; s = getchar(); }
	while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
	x *= f;
}/*}}}*/
 
template<typename _T>
void write( _T x )/*{{{*/
{
	if( x < 0 ) putchar( '-' ), x = -x;
	if( 9 < x ) write( x / 10 );
	putchar( x % 10 + '0' );
}/*}}}*/

typedef std :: pair<int, int> Occurence;

std :: vector<Occurence> occ;

int fac[MAXN], ifac[MAXN];
int c[MAXN], app[MAXN];
int N;
 
inline int Qkpow( int, int );
inline int Mul( int x, int v ) { return 1ll * x * v % mod; }
inline int Inv( const int a ) { return Qkpow( a, mod - 2 ); }
inline int Sub( int x, int v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, int v ) { return ( x += v ) >= mod ? x - mod : x; }
inline int C( int n, int m ) { return n < m ? 0 : Mul( fac[n], Mul( ifac[m], ifac[n - m] ) ); }
 
inline int Qkpow( int base, int indx )/*{{{*/
{
	int ret = 1;
	while( indx )
	{
		if( indx & 1 ) ret = Mul( ret, base );
		base = Mul( base, base ), indx >>= 1;
	}
	return ret;
}/*}}}*/
 
void Init( const int n )/*{{{*/
{
	fac[0] = 1; rep( i, 1, n ) fac[i] = Mul( fac[i - 1], i );
	ifac[n] = Inv( fac[n] ); per( i, n - 1, 0 ) ifac[i] = Mul( ifac[i + 1], i + 1 );
}/*}}}*/
 
int main()
{
	read( N ), Init( N );
	rep( i, 1, N ) read( c[i] );
	std :: sort( c + 1, c + 1 + N );
	for( int i = 1, j ; i <= N ; i = j )
	{
		for( j = i ; j <= N && c[j] == c[i] ; j ++ );
		app[j - i] ++;
	}
	rep( i, 1, N ) if( app[i] )
		occ.push_back( std :: make_pair( i, app[i] ) );
	puts( "1" );
	rep( k, 2, N )
	{
		int ans = Mul( k - 1, C( N, k ) );
		for( int i = 0 ; i < ( int ) occ.size() ; i ++ )
		{
			int q = occ[i].first;
			int res = 0;
			res = Add( res, Mul( C( N - 1, k - 1 ), q ) );
			res = Sub( res, C( N, k ) );
			res = Add( res, C( N - q, k ) );
			ans = Sub( ans, Mul( res, occ[i].second ) );
		}
		write( Add( 1, Mul( ans, Inv( C( N, k ) ) ) ) ), putchar( '\n' );
	}
	return 0;
}