[做题笔记] 鞅的停时定理

这篇文章前半部分主要讲我对该定理的感性理解,因为我感觉这东西太抽象了,对我这种数学萌新真的是极其劝退,所以只用了解到能做题的程度,并不需要严谨地掌握它。

如果你需要更严谨的认知,可以自己去翻集训队论文 鞅与一类关于停时的概率与期望问题

鞅的停时定理

鞅是一类特殊的随机过程,起源于对公平赌博游戏的数学描述。假设我们从一开始就在观察一场赌博游戏,现在已经得到了前 \(t\) 秒的观测值,那么当第 \(t+1\)观测值的期望等于第 \(t\) 秒的观测值时,我们才称这是公平赌博游戏。

对于一个随机过程 \(\{X_0,X_1,...\}\),如果 \(E(X_{n+1}|X_0,X_1,...X_n)=X_n\),我们就称这个随机过程为鞅。

随机过程可以理解成,在随机的背景下,时刻 \(i\) 会有状态 \(X_i\),把它们依次记录下来就获得了随机过程。

鞅的定义式描述的一个条件期望等式,也就是要在观测到 \(X_0,X_1,...X_n\) 的条件下才能得到这个等式。

停时定理描述的是:对于任意停时 \(t\)(理解成在这个时间结束观测),有 \(E(X_t)=X_0\)

可以一直应用鞅的定义式,递归地理解这个过程。

势能函数

考虑一个随机过程 \(\{A_0,A_1,...\}\),当确定终止状态为 \(A_t\) 的时候,怎么求出停时 \(t\) 的期望 \(E(t)\) ?方法是构造势能函数 \(\Phi(A)\) 满足下列条件:

  • \(E(\Phi(A_{n+1})-\Phi(A_n)|A_0,A_1,...A_n)=-1\)(随着时间势能减少)
  • \(\Phi(A_t)\) 为常数,且 \(\Phi(A_i)=\Phi(A_t)\) 当且仅当 \(i=t\)(末状态唯一对应着一个势能)

构造序列 \(X_i=\Phi(A_i)+i\),则 \(E(X_{n+1}|X_0,X_1,...X_n)=X_n\),所以 \(\{X_0,X_1,...\}\) 是一个鞅,那么根据停时定理,可以知道 \(E(X_t)=X_0\),那么可以描述停时 \(t\) 的期望 \(E(t)=E(\Phi(A_0))-\Phi(A_t)\)

Company Acquisitions

题目描述

点此看题

解法

\(f(x)\) 为被选中点后面跟随 \(x\) 个未被选中点的势能,设 \(\Phi(A)\) 表示整个局面的势能。

已知 \(E(\Phi(A_{n+1})-\Phi(A_n)|A_0,A_1,...A_n)=-1\),考虑现在操作的两点的管辖数量分别是 \(x,y\),那么:

\[\frac{1}{2}(f(x+1)+yf(0))+\frac{1}{2}(f(y+1)+xf(0))-f(x)-f(y)=-1 \]

\(f(0)=0\),可以解得 \(f(x)=1-2^x\),由于终态的势能就是 \(\Phi(A_t)=1-2^{n-1}\),根据势能函数的推论,那么停时 \(t\) 的期望就是 \(E(t)=\Phi(A_0)-\Phi(A_t)\),只需要暴力计算初状态的势能即可,时间复杂度 \(O(n)\)

#include <cstdio>
const int M = 505;
const int MOD = 1e9+7;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,ans,a[M];
int qkpow(int a,int b)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=r*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return r;
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		int x=read();
		if(~x) a[x]++;
	}
	for(int i=1;i<=n;i++)
		ans=(ans+1-qkpow(2,a[i])+MOD)%MOD;
	ans=(ans+qkpow(2,n-1)-1+MOD)%MOD;
	printf("%lld\n",ans);
}

Rainbow Balls

题目描述

点此看题

解法

\(m=\sum a_i\),表示球的总数。

\(f(x)\) 表示有 \(x\) 个球的颜色的势能函数,令当前局面的势能是 \(\Phi(A)=\sum f(a_i)\),考虑在一次操作中 \(m(m-1)\) 种情况中,有 \(a_i(a_i-1)+(m-a_i)(m-a_i-1)\) 种情况个数不变,有 \(a_i(m-a_i)\) 种情况变成 \(f(a_i-1)/f(a_i+1)\),那么直接列式:

\[\frac{1}{m(m-1)}\sum (a_i(a_i-1)+(m-a_i)(m-a_i-1))\cdot f(a_i)+a_i(m-a_i)(f(a_i-1)+f(a_1+1))=\sum f(a_i)-1 \]

化简一下,就可以得到:

\[\sum \frac{a_i(m-a_i)}{m(m-1)}(f(a_i-1)+f(a_i+1)-2f(a_i))=-1 \]

这个式子有点麻烦,考虑构造一组合法解。考虑到一种类似的形式 \(\sum\frac{-a_i}{m}=-1\),那么我们令下式成立即可:

\[f(a_i-1)+f(a_i+1)-2f(a_i)=\frac{-(m-1)}{m-a_i} \]

差分一下,令 \(g(i)=f(i+1)-f(i)\),可以得到:

\[g(a_i)-g(a_i-1)=\frac{-(m-1)}{m-a_i} \]

这样可以递推出 \(f(i)\),并且我们发现,终状态的势能唯一且最小(差分函数 \(g\) 单减),但是直接递推出终状态的势能是 \(O(m)\) 的,我们看能不能快速计算:

\[\Phi(A_t)=\sum_{i=0}^{m-1}\sum_{j=0}^{i}\frac{-(m-1)}{m-j}=\sum_{j=0}^{m-1}(m-j)\cdot\frac{-(m-1)}{m-j}=-m(m-1) \]

初状态的势能 \(\Phi(A_0)\) 易于计算,停时 \(t\) 的期望为 \(E(t)=\Phi(A_0)-\Phi(A_t)\),时间复杂度 \(O(\max a_i\log n)\)

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 100005;
#define int long long
const int MOD = 1e9+7;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,mx,ans,a[M],f[M],g[M];
int qkpow(int a,int b)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=r*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return r;
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();m+=a[i];
		mx=max(mx,a[i]);
	}
	g[0]=(MOD+1-m)*qkpow(m,MOD-2)%MOD;
	for(int i=1;i<=mx;i++)
	{
		g[i]=(g[i-1]+(MOD+1-m)*qkpow(m-i,MOD-2))%MOD;
		f[i]=(f[i-1]+g[i-1])%MOD;
	}
	for(int i=1;i<=n;i++)
		ans=(ans+f[a[i]])%MOD;
	ans=(ans+m*(m-1))%MOD;
	printf("%lld\n",ans);
}

School Clubs

题目描述

点此看题

解法

类似上一题列式子的方法,可以推出:

\[-\frac{x}{n}=\frac{x}{2n}f(1)+\frac{2x^2-3nx}{2n^2}f(x)+\frac{2nx-x^2}{2n^2}f(x-1)+\frac{nx-x^2}{2n^2}f(x+1) \]

\(f(1)=-2\),可以消去常数项,然后写成递推式的形式:

\[f(x+1)=\frac{3n-2n}{n-x}f(x)-\frac{x-2n}{n-x}f(x-1) \]

这样就可以 \(O(n)\) 地解决了,注意递推的时候分别维护分子和分母,由于我们只需要取得 \(O(m)\) 个点值,所以用到的时候再去做快速幂,时间复杂度 \(O(n+m\log n)\)

进一步优化需要用到多项式技巧,所以不予讨论

#include <cstdio>
#include <algorithm>
using namespace std;
const int M = 1005;
const int MOD = 998244353;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,ans,a[M];
int qkpow(int a,int b)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=1ll*r*a%MOD;
		a=1ll*a*a%MOD;
		b>>=1;
	}
	return r;
}
signed main()
{
	m=read();
	for(int i=1;i<=m;i++) n+=a[i]=read();
	sort(a+1,a+1+m);
	int b1=0,b2=1,a1=MOD-2,a2=1,p1=0,p2=0,j=1;
	while(j<=m && a[j]==1) ans=(ans+MOD-2)%MOD,j++;
	for(int i=1;i<n;i++)
	{
		p2=1ll*(n-i)*a2%MOD*b2%MOD;
		p1=(1ll*(3*n-2*i)*b2%MOD*a1%MOD
		-1ll*(2*n-i)*a2%MOD*b1%MOD)%MOD;
		p1=(p1+MOD)%MOD;
		while(j<=m && a[j]==i+1)
			ans=(ans+1ll*p1*qkpow(p2,MOD-2))%MOD,j++;
		b1=a1;b2=a2;
		a1=p1;a2=p2;
	}
	ans=(ans-1ll*p1*qkpow(p2,MOD-2))%MOD;
	printf("%d\n",(ans+MOD)%MOD);
}

总结

个人认为 鞅的停时定理 本身并不是一个很难的知识点,至少在市面上流传的例题来看,它的应用范围很有限,但是却无可替代。以后还会不会开发出更高级的应用还未可知,但是做一两道例题应该就能理解其方法。

但是该定理的问题却可以和 \(dp\)、多项式等技术融合在一起,所以可以生成综合性较强的题目。

我的建议是,在你没有遇到需要用此方法的题目时,建议不要学习此定理,以上。

posted @ 2022-06-03 21:27  C202044zxy  阅读(930)  评论(3编辑  收藏  举报