概率期望

概率与期望dp

摘自gzz学长的讲义。

线性性

X, Y为两随机变量

E(X + Y) = E(X) + E(Y)

做一些期望题时,我们通常把题目所求的分开算期望,最后加一块,这样能大大降低难度。

例一:TC SRM 595 Div1 Problem 900: Constellation

  • 题意

n个点,每个点有⼀定概率出现,问凸包的期望⾯积。⽆重复点和三点共线。

n<=50。

  • 解析

前置技能:(计算几何)三角剖分求面积

枚举每一条边所代表的面积对答案的贡献(其右侧点不能背选),然后加一块。

例二:P1850 换教室

  • 题意

有n堂课需要按顺序听,每堂课有两个教室可以选。

所有的教室都在⼀个连通带权⽆向图上。

下了⼀节课,必须要从这节课所在的教室赶往下节课所在的教室。

⼀开始这n堂课已经选好了默认的教室。

你可以提出最多m个申请,申请把某堂课的教室换到另⼀个。

申请有成功和失败的可能,会给出成功率。

必须⼀次性提出所有申请,不能⽤之前的申请结果来决定之后的申请策略。

最⼩化需要跑的路程的期望。

n ≤ 2000,m ≤ 2000,教室数⽬≤ 300

  • 解析

比较不错的期望dp练手题,可以加深对期望的理解。分九种情况大力讨论即可。

值得注意的是,如果当前申请,那么不能是

当前申请成功率 * min(上回申请, 上回不申请) + 当前申请失败率 * min(上回申请, 上回不申请)

因为当前申请时不知道能不能成功,不能探知上回申请和不申请哪一个更优,因此不能同时乘上上回的最优答案,应该拆成

min(当前申请且上回申请, 当前申请且上回不申请)

即比较暴力的分类讨论。

Code

例三:TC SRM 561 Div1 Problem 1000: Orienteering

  • 题意

给⼀棵不超过2500个点的树,树上有不超过300个关键点,

从这些关键点中随机选择k个,

然后你任意从⼀个点出发,⽤最短的路程遍历所有的这k个点,

问你的路程的期望⻓度。

  • 解析

需要一些小技巧。

容易看出要建虚树。

固定选点,答案为 边长和的二倍 - 直径长。

我们分开来考虑。

边长和二倍比较简单,树形dp可做。

直径长也可以枚举直径端点,算概率,进而算期望。

直径万一有好几条怎么办?我们可以给边权加第二维“扰动”,把每条边加上个0.0000000001 * rand(),这样就很难有多条直径了。对于这道题,用浮点数会稍微麻烦些,可以用pair代替。

这种方法同样适用于其他题中,如计算几何中加个 eps 防止有三点共线 or 两点横/纵坐标相同。但要把握好度,别影响正确性。


随机游走

求从开始到结束的期望步数

  • 做法1:设f[a]为从s到a期望走了多少步,答案即为f[t]。关系:f[s]=0,f[a]枚举所有a的前驱,乘以转移到a的概率并求和。

  • 做法2:设f[a]为从a到t期望走了多少步,答案即为f[s]。关系:f[t]=0,f[a]枚举所有a的后继,乘以a向后转移的概率并求和。

第一种符合“走路”的思考方式,但是实际仔细思考一下大有问题:

啥叫从起点到i的期望步数?

从起点到i又不是一个只发生一次的事件,甚至从起点到起点自己的期望步数都不能想当然地设成0。

那如果设成“第一次到达”呢?那转移又说不通了,融合在点a,b中的“第一次到达的步数期望”有一部分是在历史上已经到达过c了,自然不可以。

所以整个解释都是浆糊的。

所以用做法2

\[f [t] = 0 \]

\[f[x] = 1 + \sum_{x -> y}{ f[y]p(x, y) } \]

我们解决了列方程的问题,接下来,

如果原图是DAG,方程就是可以直接在DAG上拓扑序DP递推出来;

如果原图不是DAG,比如是个有环的有向图,或者甚至是个无向图,那就需要实打实的解这个方程了,用高斯消元。

求经过每个点的期望次数

这个和上一种问题完全不一样了。

想知道点x的经过的期望次数,知道x的后继的经过期望次数没有用了,有用的是知道x的前驱的经过期望次数。

所以这个是从起点开始的。

注意这个方向和上一问是反的,枚举的是前驱不是后继。

\[f[s] = 1 + \sum_{a->s}{f[a]*p(a,s)} \]

\[f[x] = \sum_{a->s}{f[a]*p(a,x)} \]

多个结束节点,问在每个结束节点结束的概率

我们可以写出概率转移矩阵,

将每个时刻的概率分布转移到下⼀时刻的概率分布,然后求它的⽆穷次⽅。

更好的做法是转化成之前的“期望次数”的问题。

我们转而求解这样一个问题:

从某个起点出发,到每个结束节点时就会停下来

问最终停下来以后经过每个点的期望次数

由于结束节点只会被经过一次,所以它的经过期望次数就是最后在它那里结束的概率

所以使用上一节的解法即可,

注意枚举的时候把所有的从终点出发的边都删了。

例题

难度单调不降排序。

P4316 绿豆蛙的归宿

随机游走的模板题,与随机游走的第一种题相同。注意,要从终点倒着dp。

将另一篇博客搬了过来。

题意:
给一张有源汇(暂且这么叫)的DAG,边有边权。
每个出度为d的节点有1/d的概率走向其任意一条出边。
求源点到汇点的期望路径长。
n <= 1e5, m <= 2e5

如果按正图建的话,是不对的。

我们以建反图的思路来做这道题。

定义f[i]为i->n的期望路径长,这样有(正图中)f[u] = Σ(u->v) ( (f[v] + len(u->v)) * P(u->v) )

让后利用反图拓扑序解决此问题。

补充:为什么正推不对?

要非得用正推,只好f[i]为1->i的期望。正反对比如下图(左逆推右顺推)(最下面的字母不用管):

绿豆蛙

这样,我们不得不记录起点到当前点i的概率P[i],不是那么好做。(但好像也可做)。

具体可见:题解 P4316 【绿豆蛙的归宿】

//p,d初始时都是出度
while (front < rear) {
	cur = que[++front];
	for (register int i = head[cur]; i; i = e[i].nxt) {
		to = e[i].to;
		f[to] += 1.0 * (f[cur] + e[i].val) / p[to];
		if (!(--d[to])) que[++rear] = to;
	}
}

P4206 [NOI2005]聪聪与可可

这道题看起来似乎很不可做的样子,有两个点在无向图上走,和三种模型都不太一样。然而我们发现:

  1. 两个点只有一个点在随机游走,另一个点的行走路线在该点固定的情况下是一定的。

  2. 由于猫的走法绝对最优,并且可以连走两次,所以不会出现一直走不完的情况,也不会出现状态的环。

于是,我们将状态设为f[u][v],表示猫在u,老鼠在v的期望步数。然后预处理出dis数组和nxt数组,记忆化搜索即可。

Code:

double dfs(int u, int v) {
	if (f[u][v])	return f[u][v];
	if (u == v)	return 0;
	if (dis[u][v] <= 2)	return f[u][v] = 1;
	int nw = nxt[u][v];
	nw = nxt[nw][v];
	int to;
	for (register int i = head[v]; i; i = e[i].nxt) {
		to = e[i].to;
		f[u][v] += (dfs(nw, to) + 1) / (d[v] + 1);
	}
	f[u][v] += (dfs(nw, v) + 1) / (d[v] + 1);
	return f[u][v];
}

P3232 [HNOI2013]游走

  • 问题转化:给一个无向连通图,1 -> n,等概率游走。求每一条边的期望经过次数g[i]。

  • 解析:

可转化成求点的期望经过次数f[i]。

(d为度数)

\[g[i] = \frac{f[u]}{d[u]} + \frac{f[v]}{d[v]} \]

不是DAG,需要高斯消元

注意n是终点,不要算它的f。

\(Code\):

inline void gx() {//高斯消元
	for (register int i = 1; i <= n; ++i) {
		int mx = i;
		for (register int j = i + 1; j <= n; ++j)
			if (F(a[j][i]) > F(a[mx][i]))	mx = j;
		if (F(a[mx][i]) <= eps)	continue;
		for (register int j = 1; j <= n; ++j) {
			if (j == mx)	continue;
			double k = a[j][i] / a[mx][i];
			for (register int p = 1; p <= n + 1; ++p)
				a[j][p] -= a[mx][p] * k;
		}
		for (register int p = 1; p <= n + 1; ++p)
			swap(a[mx][p], a[i][p]);
	}
	for (register int i = 1; i <= n; ++i)
		f[i] = a[i][n + 1] / a[i][i];
}

...

for (register int cur = 1; cur < n; ++cur) {
	a[cur][cur] = 1;
	register int to;
	for (register int i = head[cur]; i; i = e[i].nxt) {
		to = e[i].to;
		if (to == n)	continue;
		a[cur][to] = -1.0 / d[to];
	}
}
a[1][n + 1] = 1;
gx();
for (register int i = 1; i <= ecnt; i += 2) {
	if (e[i].from != n)	g[i/2] += f[e[i].from] / d[e[i].from];
	if (e[i].to != n)	g[i/2] += f[e[i].to] / d[e[i].to];
}

CF446D DZY Loves Games

首先分层图,掉血即为进入下一层。目标:\((1,K)\) -> \((n, 1)\) 的概率。

然后就成了“从一个点出发,最终到另一个点的概率”。\(O((nK)^3)\)即可完成此题。

发现分层图可以分层高消,\(O(n^3K)\)即可完成此题。

然而 \(1e9\)\(K\) 总是那么烦人。发现每层都是相似的,因此可以直接高消记录一下 \(i -> j\) 的概率。然而 \(500\) 限制了 \(n^3log\),并且如果 \(j\) 不是掉血点,就难以用“期望经过次数”来代替“概率”了。因此,考虑“缩点”,即计算出关键点到关键点的概率。注意到以不同的点作为起点,方程只是在系数上有变化,因此可以用高消的时候带着系数一起消的方法来迅速求出。这种方法类似矩阵的逆。

最后要注意:这里不能理所当然的设置起点的方程为:

\[f[S] = 1 + \sum_{from}{f[from] * p[...]} \]

因为我们要求的是从 S 出来以后在经过 \(S\) 的期望次数,不能上来给个1.因此需要把这个1分到与之连边的那些点上。

其他例题:

bzoj1444 [Jsoi2009]有趣的游戏

TC SRM 641 BitToggler


almost all by gzz

还要多做题啊。

posted @ 2021-02-23 21:23  JiaZP  阅读(362)  评论(0编辑  收藏  举报