[做题笔记] 鞅的停时定理
这篇文章前半部分主要讲我对该定理的感性理解,因为我感觉这东西太抽象了,对我这种数学萌新真的是极其劝退,所以只用了解到能做题的程度,并不需要严谨地掌握它。
如果你需要更严谨的认知,可以自己去翻集训队论文 鞅与一类关于停时的概率与期望问题
鞅的停时定理
鞅是一类特殊的随机过程,起源于对公平赌博游戏的数学描述。假设我们从一开始就在观察一场赌博游戏,现在已经得到了前 \(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\),那么:
令 \(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)\),那么直接列式:
化简一下,就可以得到:
这个式子有点麻烦,考虑构造一组合法解。考虑到一种类似的形式 \(\sum\frac{-a_i}{m}=-1\),那么我们令下式成立即可:
差分一下,令 \(g(i)=f(i+1)-f(i)\),可以得到:
这样可以递推出 \(f(i)\),并且我们发现,终状态的势能唯一且最小(差分函数 \(g\) 单减),但是直接递推出终状态的势能是 \(O(m)\) 的,我们看能不能快速计算:
初状态的势能 \(\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
题目描述
解法
类似上一题列式子的方法,可以推出:
令 \(f(1)=-2\),可以消去常数项,然后写成递推式的形式:
这样就可以 \(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\)、多项式等技术融合在一起,所以可以生成综合性较强的题目。
我的建议是,在你没有遇到需要用此方法的题目时,建议不要学习此定理,以上。