「CF850F」Rainbow Balls
题目
点这里看题目。
分析
思路一
正常思路的解法。
把整个问题看成是一个 Markov 链上行走,状态按照不同颜色各自的球数分开。这样的话,初始状态给定,终止状态为“只有一种颜色”(可能有多个终止状态)。我们相当于求的是 hitting time 的期望。
注意到,我们可以按照最终走到哪个终止状态来对路径分类。这就相当于枚举最终球都变成了哪种颜色。枚举完之后,我们就只用关心指定颜色的球数,这相当于对 Markov 链做了一个压缩。
设 \(s=\sum a\)。尝试写一个 DP:设 \(f_k\) 为指定颜色有 \(k\) 个球时,hitting time 的期望。在写转移的时候,我们需要注意 \(f_k\) 相当于求所有路径的概率乘上其长度的和,因此每走一步需要累加上从当前状态出发的路径概率之和(看作走一步,长度为 \(1\)),也就是 \(f_k\) 可以走到终止状态的概率,我们设为 \(p_k\)。因此,转移为:
边界值为 \(f_0=f_s=0\)。
Note.
这个累加的数很值得注意,因为我就是在这里错的。
究其原因,这是因为 \(f\) 对应的 Markov 链不连通,这导致了 \(p_k\neq 1\)。平常面对的 Markov 链模型通常都是连通的,因此经常都是取 \(p_k=1\)。习惯性地写下来就会出错。
还有一种更容易出错的习惯,就是写成 \(\sum_v p_{uv}(f_v+1)\) 的形式,也就是每走一步就将期望 \(+1\)。看起来很对,但是实际上也犯了同样的错误——我们仍然需要考虑走不到终点的情况。
搞笑的是,如果以为 \(p_k=1\),则可以得到 \(f_0=-1\)。这显然是错的,然而我还以为这是“答案不收敛”的表现,结果因为自大成了 Joker。
再考虑一下 \(p\) 的:
容易将第三条化简为 \(2p_k=p_{k+1}+p_{k-1}\),做一个差分即可得到 \(p\) 等差,那么差值就应该是 \(\frac{1}{s}\),进而得到 \(p_k=\frac{k}{s}\),因此:
这个等价于:
此时我们可以建立方程了。然而这必须要从 \(f_0\) 推到 \(f_s\),显然不现实的。
进一步化简的途径仍然是做差分,设 \(g_k=\Delta f_k=f_{k+1}-f_k\),我们可以得到:
边界值 \(f_s=0\) 可以转化为 \(g\) 的方程:
从而解得 \(g_0=\frac{(s-1)^2}{s}\),之后就可以下标较小的位置开始推了,复杂度为 \(O(\max a\log \text{mod})\)。
思路二
tricky 的解法。
可以证明,这个随机过程是一个“鞅”,在此不细说。
在这个前提下,通用的解法是,构造随机变量的势能函数 \(\Phi(X)\),满足以下两个条件:
-
每进行一步随机过程,\(\Delta\Phi(X)=-1\)。
-
终止状态 \(\Phi(X_{\text{end}})\) 的势能唯一且最小。
那么“停时”,也就是随机过程结束时经过操作次数的期望 \(E(t_{\text{end}})=E(\Phi(X_{\text{begin}}))-\Phi(X_{\text{end}})\)。
这道题需要将 \(\Phi(X)\) 构造成各个颜色的势能之和(每次只会影响两个颜色,个数变化量都为 \(1\))。最终的结果是,\(k\) 个球的颜色势能就是思路一中的 \(f_k\)。不过相对来说这种方法的数学技巧性(去掉理论本身)要求会低一些(比如不需要人工做差分的处理),还是比较好的。
代码
#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 = 1e9 + 7;
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' );
}
template<typename _T>
inline _T Max( const _T &a, const _T &b ) {
return a > b ? a : b;
}
int f[MAXN], g[MAXN];
int A[MAXN];
int N;
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 Qkpow( int base, int indx ) {
int ret = 1;
while( indx ) {
if( indx & 1 ) MulEq( ret, base );
MulEq( base, base ), indx >>= 1;
}
return ret;
}
int main() {
read( N );
int mx = 0, s = 0;
rep( i, 1, N ) {
read( A[i] );
mx = Max( mx, A[i] );
s += A[i];
}
g[0] = Mul( Inv( s ), Mul( s - 1, s - 1 ) );
rep( i, 1, mx )
g[i] = Sub( g[i - 1], Mul( Inv( s - i ), s - 1 ) );
f[0] = 0;
rep( i, 1, mx )
f[i] = Add( f[i - 1], g[i - 1] );
int ans = 0;
rep( i, 1, N ) AddEq( ans, f[A[i]] );
write( ans ), putchar( '\n' );
return 0;
}