「SOL」开关(LOJ)

并不套路的概率生成函数


# 题面

n 个开关初始是关闭的,当某一些开关打开而另一些开关关闭时可以开门。

随机改变开关的状态——每次有 pip 的概率更改开关 i 的状态。求期望操作多少次第一次打开门。

数据规模:pi1,p105


# 解析

- P1. 一些函数的定义

可以拆解成每一个开关最后要变成指定的状态。

pi=P。由于对于同一个开关来说,操作是无序的,应用 EGF,设 Fi(x) 是「开关 i 操作 j 次的概率 (piP)j」的 EGF。

  • i 要求最后关闭,则操作偶数次:

    Fi(x)=j=0(piP)2jx2j(2j)!=epiPx+epiPx2

  • 同理,若要求最后打开,操作奇数次:

    Fi(x)=epiPxepiPx2

从概率生成函数的角度考虑,要求期望,则设对应随机变量的概率——设 fi 为「操作 i 次打开门」的概率,对应的 OGFF(x)(普通概率生成函数才有 E(X)=F(1))。

先不管 OGF 怎么求,难道答案就是 F(1) 了吗?注意到 fi 只表示「操作 i 次打开了门」而并不是题目要求的「第一次打开门」。

gi 表示操作了 i 次后,所有开关的状态恰好不变的概率,也即操作了每个开关偶数次,记其 OGFG(x)

最后设出 hi 表示「操作 i 次后第一次打开门」的概率,其 OGFH(x),那么 H(1) 才是我们真正要求的答案。根据实际意义可以推得

H(x)G(x)=F(x)H(x)=F(x)G(x)

但是我们现在将 Fi(x) 全部乘起来只能得到 fi 的 EGF,记为 R(x)

R(x)=i=1nFi(x)

同理,我们也只能快速得到 gi 的 EGF 的表达式,记为 W(x)

W(x)=i=1nepiPx+epiPx2

考虑如何把 EGF 转成 OGF。

- P2. EGF to OGF

注意到 R(x)W(x) 的形式是类似的。

不难发现 R(x) 可以表示成如下形式(W(x) 也可以,下面只通过 R(x) 举例):

R(x)=iaieiPx

只需要把 Fi(x) 当成一对大小为 pipi 的物品做一次 O(nP) 的背包即可求出 ai

而知道了 ai 我们就可以较为方便地求出其 OGF F(x)

F(x)=k=0+k!xk[xk]R(x)=k=0+k!xk[xk]jajejPx=k=0+k!xkjaj(jP)kk!=jajk=0+(jxP)k=jaj1jxP

上式的 j 的取值根据背包可知应为 [P,P]

- P3. 求导

根据之前的推导,我们已经可以求得 OGF F(x),G(x),而最终目标

H(x)=F(x)G(x)

对其求导并代入 x=1 可以得到答案:

H(x)=F(x)G(x)G(x)F(x)G2(x)H(1)=F(1)G(1)G(1)F(1)G2(1)

注意 F(x),G(x) 都不涵盖所有可能情况,所以 F(1)1,G(1)1

但是稍微细心一些,我们发现 F(x)G(x)1 处很有可能不收敛——在 P2 中求出的式子:

F(x)=jaj1jxP

aP0,有 j=PF(x)1 处不收敛;G(x) 同理。

正无穷除以正无穷?洛必达

考虑 H(x)=F(x)G(x) 分子分母同时乘以 (1x),或者说 F(x)G(x) 的表达式同时乘以 (1x),可以消去不收敛的项。

又因为 H(x) 是一个形式幂级数,x 能取什么值并不重要,所以这样的操作是可以的。

乘上 (1x) 后,注意导函数在 x=1 处的取值会变成相反数。


# 源代码

Copy/*Lucky_Glass*/
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

inline int rin(int &r){
	int b=1,c=getchar();r=0;
	while(c<'0' || '9'<c) b=c=='-'?-1:b,c=getchar();
	while('0'<=c && c<='9') r=(r<<1)+(r<<3)+(c^'0'),c=getchar();
	return r*=b;
}

const int N=105,M=5e4+10,MOD=998244353;
#define con(type) const type &

inline int add(con(int)a,con(int)b){return a+b>=MOD?a+b-MOD:a+b;}
inline int sub(con(int)a,con(int)b){return a-b<0?a-b+MOD:a-b;}
inline int mul(con(int)a,con(int)b){return int(1ll*a*b%MOD);}
inline int ina_pow(con(int)a,con(int)b){return b?mul(ina_pow(mul(a,a),b>>1),(b&1)?a:1):1;}
inline int inv(con(int)k){return ina_pow(k,MOD-2);}

int n,sump;
int ars[N],arp[N],f[2][M<<1],g[2][M<<1];

int main(){
	const int INV2=ina_pow(2,MOD-2);
	rin(n);
	for(int i=1;i<=n;i++) rin(ars[i]);
	for(int i=1;i<=n;i++) sump+=rin(arp[i]);
	f[0][sump]=g[0][sump]=1;
	for(int i=1,now=0;i<=n;now+=arp[i],i++){
		int I=i&1,J=!I;
		fill(f[I]+(sump-now-arp[i]),f[I]+(sump+now+arp[i])+1,0);
		fill(g[I]+(sump-now-arp[i]),g[I]+(sump+now+arp[i])+1,0);
		int *fI=f[I]+sump,*fJ=f[J]+sump,*gI=g[I]+sump,*gJ=g[J]+sump;
		for(int j=-now;j<=now;j++){
			int val=mul(INV2,fJ[j]);
			fI[j+arp[i]]=add(fI[j+arp[i]],val);
			if(ars[i]) fI[j-arp[i]]=sub(fI[j-arp[i]],val);
			else fI[j-arp[i]]=add(fI[j-arp[i]],val);
			val=mul(INV2,gJ[j]);
			gI[j+arp[i]]=add(gI[j+arp[i]],val);
			gI[j-arp[i]]=add(gI[j-arp[i]],val);
		}
	}
	int *fi=f[n&1]+sump,*gi=g[n&1]+sump;
	int invs=ina_pow(sump,MOD-2),f0=fi[sump],g0=gi[sump],f1=0,g1=0;
	for(int i=-sump;i<=sump;i++){
		int ii=i<0? i+MOD:i,tmp=inv(sub(mul(ii,invs),1));
		f1=add(f1,mul(fi[i],tmp));
		g1=add(g1,mul(gi[i],tmp));
	}
	printf("%d\n",mul(sub(mul(f1,g0),mul(g1,f0)),inv(mul(g0,g0))));
	return 0;
}

THE END

Thanks for reading!

如若是狂风雪雨
亦或碧空如洗
此生百转千回阴晴只得由你
挥毫执墨笔 把你记作曲
也许 此中音律抚琴为君高歌一曲

——《琴弦上(vocaloid)》By 乐正绫/赤羽/星葵

> Link 琴弦上-Bilibili

posted @   Lucky_Glass  阅读(1228)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
TOP BOTTOM
点击右上角即可分享
微信分享提示