「CF850F」Rainbow Balls

题目

点这里看题目。

分析

思路一

正常思路的解法。

把整个问题看成是一个 Markov 链上行走,状态按照不同颜色各自的球数分开。这样的话,初始状态给定,终止状态为“只有一种颜色”(可能有多个终止状态)。我们相当于求的是 hitting time 的期望。

注意到,我们可以按照最终走到哪个终止状态来对路径分类。这就相当于枚举最终球都变成了哪种颜色。枚举完之后,我们就只用关心指定颜色的球数,这相当于对 Markov 链做了一个压缩。

\(s=\sum a\)。尝试写一个 DP:设 \(f_k\) 为指定颜色有 \(k\) 个球时,hitting time 的期望。在写转移的时候,我们需要注意 \(f_k\) 相当于求所有路径的概率乘上其长度的和,因此每走一步需要累加上从当前状态出发的路径概率之和(看作走一步,长度为 \(1\)),也就是 \(f_k\) 可以走到终止状态的概率,我们设为 \(p_k\)。因此,转移为:

\[f_k=\frac{1-2k(s-k)}{s(s-1)}f_k+\frac{k(s-k)}{s(s-1)}f_{k+1}+\frac{k(s-k)}{s(s-1)}f_{k-1}+p_k,0<k<s \]

边界值为 \(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\) 的:

\[p_k= \begin{cases} 0&k=0\\ 1&k=s\\ \frac{1-2k(s-k)}{s(s-1)}p_k+\frac{k(s-k)}{s(s-1)}(p_{k+1}+p_{k-1})&\text{otherwise} \end{cases} \]

容易将第三条化简为 \(2p_k=p_{k+1}+p_{k-1}\)做一个差分即可得到 \(p\) 等差,那么差值就应该是 \(\frac{1}{s}\),进而得到 \(p_k=\frac{k}{s}\),因此:

\[f_k=\frac{1-2k(s-k)}{s(s-1)}f_k+\frac{k(s-k)}{s(s-1)}f_{k+1}+\frac{k(s-k)}{s(s-1)}f_{k-1}+\frac{k}{s},0<k<s \]

这个等价于:

\[2f_k=f_{k+1}+f_{k-1}+\frac{s-1}{s-k},0<k<s \]

此时我们可以建立方程了。然而这必须要从 \(f_0\) 推到 \(f_s\),显然不现实的。

进一步化简的途径仍然是做差分,设 \(g_k=\Delta f_k=f_{k+1}-f_k\),我们可以得到:

\[g_k=g_0-\sum_{j=1}^{k}\frac{s-1}{s-j} \]

边界值 \(f_s=0\) 可以转化为 \(g\) 的方程:

\[f_s=\sum_{k=0}^{s-1}g_k=\sum_{k=0}^{s-1}\left(g_0-\sum_{j=1}^k\frac{s-1}{s-k}\right) \]

从而解得 \(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;
}
posted @ 2022-07-21 21:00  crashed  阅读(57)  评论(0编辑  收藏  举报