题目
点这里看题目。
分析
这里有一个很重要的转化——如果某一列已经满了,我们仍然认为它可以 " 装 " 人,只不过没有效果而已。
对于某个操作序列,我们现在可以只看有效的操作。那么有 (p) 列已满的情况下,选出 (n-p) 中某一列概率为:
[sum_{k=0}^{+infty}left(frac{p}{n}
ight)^kfrac{1}{n}=frac{1}{n(1-frac{p}{n})}=frac{1}{n-p}
]
看起来很合理,不是吗?
于是回到原问题,我们可以枚举某一列求解,不妨为第 (i) 列。此时就相当于操作序列中,恰好有 (a_i) 个 (i) 且最后一个为 (i) ,此外对于所有 (j ot =i) 都有至少 (a_j) 个 (j) ;不难列出这种情况的生成函数:
[F_i(x)=frac{1}{n}cdotfrac{x^{a_i-1}}{n^{a_{i}-1}(a_i-1)!}prod_{j
ot= i}(e^{frac{x}{n}}-sum_{k=0}^{a_j-1}frac{x^k}{n^kk!})
]
最终,对于 (F_i(x)=sum_{kge 0}f_{i,k}frac{x^k}{k!}) ,我们得到答案为 (sum_{kge 0}f_{i,k}=sum_{kge 0}k![x^k]F_i(x)) 。
注意到 (F_i(x)) 的形式其实是多项 (alpha x^eta e^{frac{gamma}{n}x}) 之和,我们可以单独对每一项进行计算并求和。而对于其中一项,可以得到:
[alpha x^eta e^{frac{gamma}{n}x}=alphasum_{kge 0} frac{(frac{gamma}{n})^{k}x^{k+eta}}{k!}
]
那么它对答案的贡献为:
[egin{aligned}
alphasum_{kge 0}frac{gamma^{k}}{n^{k}}cdot (k+eta)^{underline eta}
&=alphacdot eta!sum_{kge 0}inom{k+eta}{k}frac{gamma^{k}}{n^{k}}\
&=alphacdot eta!sum_{kge 0}(-1)^kinom{k+(eta+1)-1}{k}left(-frac{gamma}{n}
ight)^k\
&=alphacdot eta!(1-frac gamma n)^{-k}=alphacdot eta!left(frac{n}{n-gamma}
ight)^k
end{aligned}
]
这里是广义二项式定理,不要用成常规二项式定理了。
因此我们可以对于每一种 (x^{eta}e^{frac{gamma}{n}x}) 计算它的 (alpha) ,然后就可以愉快地计算了!
如果暴力做可能会变成 (O(n^4(sum a)^2)) ,做一个简单的退背包就可以优化到 (O(n^3(sum a)^2)) 。
小结:
- 最开始的转化相当重要(也很巧妙),在 [PKUWC2018]猎人杀 中也有相似的操作。
- 对于 (x^{eta}e^{frac{gamma}{n}x}) 的相似单项的处理,是很常用的方法。
代码
#include <cstdio>
#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 = 35, MAXM = 905;
template<typename _T>
void read( _T &x )
{
x = 0; char s = getchar(); int f = 1;
while ( s < '0' || '9' < s ) { 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' );
}
int dp[MAXN][MAXM], tmp[MAXN][MAXM];
int fac[MAXM], ifac[MAXM], inv[MAXM];
int A[MAXN];
int N, M;
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; }
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()
{
fac[0] = 1; rep( i, 1, M ) fac[i] = Mul( fac[i - 1], i );
ifac[M] = Inv( fac[M] ); per( i, M - 1, 0 ) ifac[i] = Mul( ifac[i + 1], i + 1 );
inv[1] = 1; rep( i, 2, M ) inv[i] = Mul( inv[mod % i], mod - mod / i );
}
int main()
{
read( N ), M = 0;
rep( i, 1, N ) read( A[i] ), M += A[i];
Init(), dp[0][0] = 1;
rep( i, 1, N )
per( j, N, 0 )
per( k, M, 0 )
{
dp[j][k] = mod - dp[j][k];
if ( j ) dp[j][k] = Add( dp[j][k], dp[j - 1][k] );
for ( int s = 1, pw = inv[N] ; s < A[i] && s <= k ; s ++, pw = Mul( pw, inv[N] ) )
dp[j][k] = Sub( dp[j][k], Mul( dp[j][k - s], Mul( ifac[s], pw ) ) );
}
int ans = 0;
rep( i, 1, N )
{
ans = 0;
rep( j, 0, N )
rep( k, 0, M )
{
tmp[j][k] = dp[j][k];
if ( j ) tmp[j][k] = Sub( tmp[j][k], tmp[j - 1][k] );
for ( int s = 1, pw = inv[N] ; s < A[i] && s <= k ; s ++, pw = Mul( pw, inv[N] ) )
tmp[j][k] = Add( tmp[j][k], Mul( tmp[j][k - s], Mul( ifac[s], pw ) ) );
tmp[j][k] = mod - tmp[j][k];
}
rep( j, 0, N )
{
int pw = Qkpow( inv[N], A[i] );
per( k, M, A[i] - 1 )
tmp[j][k] = Mul( tmp[j][k - A[i] + 1], Mul( ifac[A[i] - 1], pw ) );
rep( k, 0, A[i] - 2 ) tmp[j][k] = 0;
}
rep( m, 0, N - 1 )
{
int stp = Mul( N, inv[N - m] );
for ( int t = 0, pw = stp ; t <= M ; t ++, pw = Mul( pw, stp ) )
ans = Add( ans, Mul( Mul( pw, fac[t] ), tmp[m][t] ) );
}
write( ans ), putchar( i == N ? '
' : ' ' );
}
return 0;
}