「CF1392H」ZS Shuffles Cards
题目
点这里 看题目。
你有 \(n+m\) 张牌,其中有恰好 \(n\) 张为数字牌,分别标有 \(1,2,3,\dots,n\),剩下的恰好 \(m\) 张均为鬼牌。
一开始,牌被随机打乱,同时你有一个集合 \(S=\varnothing\)。接下来,你将要进行如下操作若干轮:
一轮操作中,你需要从牌堆里选出一张牌:
如果是数字牌,假设牌上标有数字 \(x\),则你需要将它从牌堆中取出放到一边,并令 \(S\gets S\cap \{x\}\)。
如果是鬼牌,则将所有的牌放回牌堆并重新洗牌,所以洗完牌后牌堆里仍然有 \(n+m\) 张牌。
此时,如果 \(S=\{1,2,3,\dots,n\}\),操作结束。否则,\(S\) 不变,你需要继续抽牌。
设 \(X\) 为操作结束时经过的轮数,你需要求出 \(E[x]\bmod 998244353\)。
所有数据满足 \(1\le n,m\le 2\times 10^6\)。
分析
三年前不会做,三年后差点还是不会做。
记 \([n]=\{1,2,3,\dots,n\}\)。将操作过程看作:每次以一定概率取出一个集合 \(T\subseteq [n]\),并令 \(S\gets S\cap T\);如果某次操作完后 \(S=[n]\) 则停止,\(X\) 即为 \(\sum |T|\)。
这个结构让人想到 min-max 反演。设 \(X_k\) 表示编号为 \(k\) 的牌第一次出现在集合 \(S\) 中时,已经出现过的 \(T\) 的大小之和,则 \(X=\max_{1\le k\le n}\{X_k\}\)。对它做反演,我们要求的就是 \(E[\min_{k\in A} X_k](A\subseteq [n],A\neq \varnothing)\)。
答案显然只和 \(|A|\) 有关,所以设 \(s=|A|,E_s=E[\min_{k\in A}X_k]\)。于是,考察每一种大小的集合出现的概率:任意一种大小为 \(k\) 的集合的出现概率为 \(q_k=\frac{n^{\underline k}m}{(n+m)^{\underline{k+1}}}\),任意一种大小为 \(k\) 的不包含 \(A\) 中元素的集合的出现概率为 \(p_k=\frac{(n-s)^{\underline k}m}{(n+m)^{\underline{k+1}}}\)。另设 \(P=\sum_k p_k\),则可以得到:
\(\sum_k (k+1)q_k\) 与 \(s\) 无关,所以我们重点考虑求 \(P\)。不妨设 \(r=n-s\),那么有:
于是就可以 \(O(1)\) 求出来了,于是就可以 \(O(n)\) 计算答案了。
进行后续数学推导也可以得到封闭结果,不过没有必要了。
代码
#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, inv2 = ( mod + 1 ) >> 1;
const int MAXN = 4e6 + 5;
template<typename _T>
inline void Read( _T &x ) {
x = 0; char s = getchar(); bool f = false;
while( ! ( '0' <= s && s <= '9' ) ) { f = s == '-', s = getchar(); }
while( '0' <= s && s <= '9' ) { x = ( x << 3 ) + ( x << 1 ) + ( s - '0' ), s = getchar(); }
if( f ) x = -x;
}
template<typename _T>
inline void Write( _T x ) {
if( x < 0 ) putchar( '-' ), x = -x;
if( 9 < x ) Write( x / 10 );
putchar( x % 10 + '0' );
}
int P[MAXN];
int fac[MAXN], ifac[MAXN], inv[MAXN];
int N, M;
inline int Qkpow( int, int );
inline int Inv( const int &a ) { return Qkpow( a, mod - 2 ); }
inline int Mul( int x, const int &v ) { return 1ll * x * v % mod; }
inline int Sub( int x, const int &v ) { return ( x -= v ) < 0 ? x + mod : x; }
inline int Add( int x, const int &v ) { return ( x += v ) >= mod ? x - mod : x; }
inline int& MulEq( int &x, const int &v ) { return x = 1ll * x * v % mod; }
inline int& SubEq( int &x, const int &v ) { return ( x -= v ) < 0 ? ( x += mod ) : x; }
inline int& AddEq( int &x, const int &v ) { return ( x += v ) >= mod ? ( x -= mod ) : x; }
inline int S1( int n ) { return Mul( inv2, Mul( n, n + 1 ) ); }
inline int C( int n, int m ) { return n < m ? 0 : Mul( fac[n], Mul( ifac[m], ifac[n - m] ) ); }
inline int CInv( int n, int m ) { return n < m ? 0 : Mul( ifac[n], Mul( fac[m], fac[n - m] ) ); }
inline int Qkpow( int base, int indx ) {
int ret = 1;
while( indx ) {
if( indx & 1 ) MulEq( ret, base );
MulEq( base, base ), indx >>= 1;
}
return ret;
}
inline 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 );
rep( i, 1, n ) inv[i] = Mul( ifac[i], fac[i - 1] );
}
int main() {
Read( N ), Read( M ), Init( N + M );
int Q = 0, tmp;
rep( x, 0, N ) {
P[x] = Mul( Mul( M, inv[N + M] ), Mul( C( N + M, N + M - x ), CInv( N + M - 1, x ) ) );
tmp = Mul( Mul( M, inv[N + M] ), Mul( C( N, x ), CInv( N + M - 1, x ) ) );
AddEq( Q, Mul( tmp, x + 1 ) );
}
int ans = 0;
rep( s, 1, N ) {
int f = Mul( Inv( Sub( 1, P[N - s] ) ), Q );
s & 1 ? AddEq( ans, Mul( C( N, s ), f ) ) : SubEq( ans, Mul( C( N, s ), f ) );
}
Write( ans ), putchar( '\n' );
return 0;
}