[学习笔记] 概率与期望及其应用


前言

这是一篇初学者的学习笔记,可能有些不准确或者遗漏的地方,还请各位指出。

可以通过 目录 或者 Ctrl + F 寻找所需内容。

点击展开目录

目录
  • https://www.luogu.com.cn/problem/P4316

    4.1.1 题目大意

    给一张 \(n\) 个点 \(m\) 条边的有向图,每条边有边权。现从 \(1\) 走到 \(n\),每次等概率选取一条边走,求路径总长度的期望。

    4.1.2 思路

    考虑进行期望DP。

    \(f_i\) 表示从点 \(i\) 出发走到点 \(n\) 的期望路径长度,答案即为 \(f_1\)。初始状态 \(f_n=0\)

    反向连边建图,在图上跑拓扑进行转移。

    具体地讲,每次取出一个入度为零的点 \(x\),枚举它能到的点 \(v\),在正常拓扑的同时转移,转移式为 \(f_v=\frac{f_x+w(x,v)} {deg_v}\)

    4.1.3 代码实现

    int n, m;
    int last[N], cnt;
    struct edge {
    	int to, next, w;
    } e[N << 1];
    void addedge(int x, int y, int w) {
    	e[++cnt].to = y;
    	e[cnt].next = last[x];
    	e[cnt].w = w;
    	last[x] = cnt;
    }
    int deg[N], lne[N]; //deg为拓扑所用的入度数, lne为出边数 
    queue  s;
    double f[N];
    void topsort() {
    	for (int i = 1; i <= n; i++)
    		if (deg[i] == 0) s.push(i);
    	while (s.size()) {
    		int x = s.front(); s.pop();
    		for (int i = last[x]; i; i = e[i].next) {
    			int v = e[i].to; deg[v]--;
    			f[v] += (f[x] + e[i].w) * 1.0 / lne[v];
    			if (!deg[v]) s.push(v);
    		}
    	}
    }
    int main() {
    	n = read(), m = read();
    	for (int i = 1; i <= m; i++) {
    		int u = read(), v = read(), w = read();
    		addedge(v, u, w); deg[u]++, lne[u]++; //反向建边 
    	}
    	topsort();
    	printf("%.2lf", f[1]);
    	return 0;
    }
    

    4.2 [NOIP2016 提高组] 换教室

    https://www.luogu.com.cn/problem/P1850

    4.2.1 题目大意

    一共有 \(n\) 个时间节点上安排了课程,对于每个时间节点 \(i\),两节内容相同的课会占用 \(c_i\)\(d_i\) 两间教室。

    一般来讲,学生需按时间在 \(c_i\) 教室完成第 \(i\) 节课。但他们也可以通过提交申请尝试更换教室。具体地,申请更换第 \(i\) 节课的教室通过的概率为已知实数 \(k_i\),如果申请通过,学生就可以去 \(d_i\) 教室上课。

    牛牛可以提交最多 \(m\) 次申请。由于两教室间的距离和拥堵程度不同,牛牛在前往教室时耗费的体力也不同。当第 \(i(1\leq i 节课结束后,他会从这间教室沿耗费体力最少的路径前往下个教室。

    问申请更换教室后 在教室间移动耗费的体力值的总和 的期望值 最小是多少。

    4.2.2 思路

    需要知道每两间教室直接的最短路长度时多少。这可以用 Floyed 解决。

    然后考虑DP,设 \(f_{i,j,0/1}\) 表示前 \(i\) 个时间节点换了 \(j\) 次教室,第 \(i\) 个时间节点 换/没换 教室,耗费的体力值的总和的期望最小是多少。

    怎么转移?

    \(f_{i,j,0}=\min\{f_{i-1,j,0}+dis(c_{i-1},c_i),f_{i-1,j,1}+dis(d_{i-1},c_i)\times k_{i-1}+dis(c_{i-1},c_i)\times(1-k_{i-1}) \}\)

    \(f_{i,j,1}=\min\{f_{i-1,j-1,0}+dis(c_{i-1},d_i)\times k_i+dis(c_{i-1},c_i)\times(1-k_i),\)\(f_{i-1,j-1,1}+dis(d_{i-1},d_i)\times k_{i-1}\times k_i+dis(d_{i-1},c_i)\times k_{i-1}\times(1-k_i)\)\(+dis(c_{i-1},d_i)\times(1-k_{i-1})\times k_i+dis(c_{i-1},c_i)\times(1-k_{i-1})\times(1-k_i) \}\)

    答案即为 \(\min_{i=0}^mmin(f_{n,i,0},f_{n,i,1})\)

    4.2.3 代码实现

    dp转移太长了,代码很丑,见谅~

    const int N = 2010, M = 90010;
    const double INF = 1e17;
    int n, m, cntroom, cntedge, c[N], d[N];
    ll dis[N][N];
    double f[N][N][2], k[N];
    int main() {
    	n = read(), m = read(), cntroom = read(), cntedge = read();
    	for (int i = 1; i <= cntroom; i++)
    		for (int j = i + 1; j <= cntroom; j++)
    			dis[i][j] = dis[j][i] = INF;
    	for (int i = 1; i <= n; i++) c[i] = read();
    	for (int i = 1; i <= n; i++) d[i] = read();
    	for (int i = 1; i <= n; i++) scanf("%lf", &k[i]);
    	for (int i = 1; i <= cntedge; i++) {
    		int u = read(), v = read(), w = read();
    		dis[u][v] = dis[v][u] = min(dis[u][v], w * 1ll);
    	}
    	for (int p = 1; p <= cntroom; p++)
    		for (int i = 1; i <= cntroom; i++)	
    			for (int j = 1; j <= cntroom; j++)
    				dis[i][j] = min(dis[i][j], dis[i][p] + dis[p][j]);
    	for (int i = 0; i <= n; i++)
    		for (int j = 0; j <= m; j++)
    			f[i][j][0] = f[i][j][1] = INF;
    	f[1][0][0] = f[1][1][1] = 0;
    	for (int i = 2; i <= n; i++) f[i][0][0] = f[i - 1][0][0] + dis[c[i - 1]][c[i]];
    	for (int i = 2; i <= n; i++)
    		for (int j = 1; j <= min(i, m); j++) {
    			f[i][j][0] = min(f[i - 1][j][0] + dis[c[i - 1]][c[i]], f[i - 1][j][1] + dis[d[i - 1]][c[i]] * k[i - 1] + dis[c[i - 1]][c[i]] * (1 - k[i - 1]));
    			f[i][j][1] = min(f[i - 1][j - 1][0] + dis[c[i - 1]][d[i]] * k[i] + dis[c[i - 1]][c[i]] * (1 - k[i]), f[i - 1][j - 1][1] + dis[d[i - 1]][d[i]] * k[i - 1] * k[i] + dis[d[i - 1]][c[i]]* k[i - 1] * (1 - k[i]) + dis[c[i - 1]][d[i]] * (1 - k[i - 1]) * k[i] + dis[c[i - 1]][c[i]] * (1 - k[i - 1]) * (1 - k[i]));
    		}
    	double ans = INF;
    	for (int i = 0; i <= m; i++) ans = min(ans, min(f[n][i][0], f[n][i][1]));
    	printf("%.2lf\n", ans);
    	return 0;
    }
    

    参考资料

    蒙提霍尔问题(又称三门问题、山羊汽车问题)的正解是什么? - 知乎

    概率与期望及其应用 - 曹文