鞅的停时定理
看来还得再推迟一点。今天是鞅的停时定理。
在此之前先挂个假人:
我:今天T3算不算科技人被科技打败了)
某人:
大概吧(
但我也不算科技人啊
我是个废物 科技打败了废物
科技揍扁了废物
名字就不透露了,防止被 d。啊好像已经被 d 了。后续可以去他那里找。
总之今天 T3 大概算是科技人被科技打败了。但是科技人不死于徒手所以来补一下这个点。(某种程度上这个也算是我曾经看过但是看不懂所以弃了的东西之一)
然后发现一个问题,就是鞅的停时定理这东西关键不在定理,在势能函数。也就是说你就算不知道这是个什么东西也能构造函数求解。
离散时间鞅
开始抄定义。
这是一个时间离散的随机过程 \(\{X_0,X_1,X_2,\cdots\}\),使得对于 \(\forall n\in \mathbb{N}\) 满足:
- \(E(|X_n|)<\infty\);
- \(E(X_{n+1}-X_n|X_0,X_1,\cdots,X_n)=0\)。
第二个就是条件期望,在已知 \(X_0-X_n\) 的情况下有 \(E(X_{n+1}-X_n)=0\)。那么容易推出来 \(E(X_n)=X_0\)。
停时
停时:关于随机过程 \(\{X_0,X_1,\cdots\}\) 的停时是一个非负随机变量 \(T\) (可以为 \(\infty\)),满足对于任意 \(n\) ,\([n=T]\) 的取值仅与 \(X_0,X_1,\cdots,X_n\) 有关。
带停时的随机过程:对于随机过程 \(\{X_0,X_1,\cdots\}\) 的停时为 \(T\) ,则其带停时的随机过程 \(\{Y_0,Y_1,\cdots\}\) 为:
就是在停时之后不变。
鞅的停时定理
设鞅 \(\{X_0,X_1,\cdots\}\) 的停时为 \(T\) ,当下列三个条件之一成立时,有 \(E(X_T)=X_0\):
- \(T\) 几乎必然有界(即存在常数 \(k\) 满足 \(P(T\le k)=1\));
- \(|X_{i+1}-X_i|\) 一致有界,\(E(T)\) 有限(即存在常数 \(k\) 满足 \(P(E(|X_{i+1}-X_i)\le k)=1\));
- \(Y_i\) (\(X_i\) 的带停时的随机过程)几乎必然有界。
看起来好乱。不过用法和上边那个东西似乎关系不是很大。
这玩意的一般用法是构造势能函数 \(\phi(A)\) 满足 \(E(\phi(A_{n+1})-\phi(A_n)|A_0,A_1,\cdots,A_n)=-1\) 且 \(\phi(A_n)\) 为常数,那么随机事件 \(\{X_0,X_1,\cdots\}\) 满足 \(X_n=\phi(A_n)+n\) 即是鞅。根据停时定理,\(E(X_n)=E(X_0)\),也就是说
感性理解一下就是每次操作一步势能就 \(-1\),那么初势能 \(-\) 末势能就是期望步数。
用到这个东西的题好像都是让你求一个事件的期望终止时间。这时候就把答案当做停时然后构造势能函数。举几个例子。
CF1349D Slime and Biscuits
题意:有 \(n\) 个人,第 \(i\) 个有 \(a_i\) 个饼干,每次随机选一个饼干随机给剩下 \(n-1\) 个人中的一个,当一个人有所有饼干时停止,求期望步数。
以下设 \(m=\sum a_i\)。
仍然构造势能函数 \(\phi(A_T)=\sum_{i=1}^nf(a_i)\) ,其中 \(a_i\) 是 \(T\) 步后第 \(i\) 个人的饼干数。
那么我们要让 \(E(A_{n+1}-A_n|A_0,A_1,\cdots,A_n)=-1\)。一步内势能减少 \(1\),那么我们只需要找到执行一步前的势能和执行一步后的势能。如果执行一步前的势能是 \(\sum_{i=1}^nf(a_i)\),那么枚举选中了谁的饼干和分给哪个人,有:
每个人有 \(\frac{a_i}m\) 的概率被选中拿出饼干,然后枚举剩下的人,每个人有 \(\frac 1{n-1}\) 概率拿到饼干,剩下的概率拿不到饼干。全都加起来就行了。
然后开始化右边的式子。
单独考虑一项:
于是可以递推 \(f\) 了。将 \(x=0\) 代入得到 \(f(0)=f(1)\),所以这俩取什么数并不影响答案。为了方便我们可以取 \(0\)(如果不信可以试一下,反正我初始化 \(114514\) 是没有问题的)。
最后用初势能减去末势能就可以了。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int mod=998244353;
int n,m,ans,a[5000010],b[5000010],f[10000010],inv[10000010];
int main(){
scanf("%d",&n);inv[1]=1;
for(int i=1;i<=n;i++)scanf("%d",&a[i]),m+=a[i];
for(int i=2;i<=max(n,m);i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(int i=1;i<m;i++){
f[i+1]=(1ll*(1-1ll*(m-i)*(n-2)%mod*inv[m]%mod*inv[n-1]%mod+mod)%mod*f[i]%mod-1ll*i*inv[m]%mod*(f[i-1]+1)%mod+mod)%mod;
f[i+1]=1ll*f[i+1]*m%mod*(n-1)%mod*inv[m-i]%mod;
}
for(int i=1;i<=n;i++)ans=(ans+f[a[i]])%mod;
printf("%d\n",(ans-f[m]+mod)%mod);
return 0;
}
CF1025G Company Acquisitions
这个题还好,比上边那个顺手不少。
套路设 \(f(x)\) 为有 \(x\) 个儿子的势能,那么 \(n\) 步的势能 \(\phi(n)\) 就是所有被选中的节点的势能之和。
考虑一次操作前后的势能。假设两个分别有 \(x,y\) 个儿子的节点被选中,那么易得
为得到递推式,代入 \(y=x\),得到
\(f(0)\) 仍然不影响,所以为方便可以设为 \(0\) ,那么得到通项 \(f(x)=1-2^x\)。算就完了。
(所以为啥这题 \(n\le 500\))
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int mod=1000000007;
int n,ans,cnt[510];
bool v[510];
int qpow(int a,int b){
int ans=1;
while(b){
if(b&1)ans=1ll*ans*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return ans;
}
int f(int x){
return (1-qpow(2,x)+mod)%mod;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
int x;scanf("%d",&x);
if(~x)cnt[x]++;
else v[i]=true;
}
for(int i=1;i<=n;i++){
if(v[i])ans=(ans+f(cnt[i]))%mod;
}
ans=(ans-f(n-1)+mod)%mod;
printf("%d\n",ans);
}
CF850F Rainbow Balls
这玩意 tm 推一半给我整出公式恐惧症来了。今天的数学就先到这里。
仍然套路设 \(f(a_i)\) 是操作 \(n\) 次之后颜色 \(i\) 的势能。套路找操作后的势能:
进行一大堆粗糙的体力活之后可以得到通项(这段我实在不想写了球球了放过我吧):
然而这 \(O(\sum a_i)\) 的复杂度看上去就很不靠谱。实际上也确实很不靠谱。所以继续整一点很神奇的转化。
差分一下,设 \(g(x)=f(x)-f(x-1)\),那么
接下来就是找个合适的初值方便算数。主要的麻烦是算 \(f(m)\)。代一下发现 \(f(m)=f(0)+mg(0)-m(m-1)\)。如果取 \(f(0)=0,g(0)=m-1\),那么 \(f(m)=0\)。剩下的可以递推。
#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int mod=1000000007;
int n,m,ans,a[2510],f[100010];
int qpow(int a,int b){
int ans=1;
while(b){
if(b&1)ans=1ll*ans*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return ans;
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),m+=a[i];
f[0]=0;f[1]=1ll*(m-1)*(m-1)%mod*qpow(m,mod-2)%mod;
for(int i=1;i<100000;i++)f[i+1]=(2ll*f[i]-f[i-1]-1ll*(m-1)*qpow(m-i,mod-2)%mod+mod+mod)%mod;
for(int i=1;i<=n;i++)ans=(ans+f[a[i]])%mod;
printf("%d\n",ans);
return 0;
}