只要你跑的够快,锅就追不上你

0%

「专题选做」概率与期望题目选做 1

TorusSailing

「Topcoder 12984」TorusSailing

题目描述

有 $n \times m$ 的网格,一人从 $(1, 1)$ 随机游走,每次等概率向下或向右,问走到 $(x, y)$ 的期望步数。

数据范围:$n, m \le 100$。

思路分析

$f(i, j)$ 表示 $(i, j)$ 走左、上游走到 $(1, 1)$ 的期望时间。发现所有两维坐标都 $\ge 2$ 的 $f(a, b)$ 都可以用 $f(1, i)$ 和 $f(i, 1)$ 的线性组合表出,于是我们列出关于 $f(1, i)$ 和 $f(i, 1)$ 的方程并用高斯消元解出即可,时间复杂度 $O(n^3)$。

代码实现

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
typedef double db;
5
const int maxn = 200;
6
7
class TorusSailing {
8
public:
9
	int n, m, x, y;
10
	db a[maxn + 3][maxn + 3][maxn + 3], f[maxn + 3][maxn + 3], res[maxn + 3];
11
12
	void gauss(db a[][maxn + 3], int n) {
13
		for (int i = 1; i <= n; i++) {
14
			int id = i;
15
			for (int j = i + 1; j <= n; j++) {
16
				if (a[j][i] > a[id][i]) id = j;
17
			}
18
			if (id != i) {
19
				swap(a[id], a[i]);
20
			}
21
			for (int j = i + 1; j <= n; j++) {
22
				db t = a[j][i] / a[i][i];
23
				for (int k = i; k <= n; k++) {
24
					a[j][k] -= t * a[i][k];
25
				}
26
				a[j][0] -= t * a[i][0];
27
			}
28
		}
29
		for (int i = n; i; i--) {
30
			res[i] = a[i][0] / a[i][i];
31
			for (int j = 1; j < i; j++) {
32
				a[j][0] -= res[i] * a[j][i];
33
			}
34
		}
35
	}
36
37
	db expectedTime(int N, int M, int X, int Y) {
38
		n = N, m = M, x = X + 1, y = Y + 1;
39
		for (int i = 2; i <= n; i++) {
40
			a[i][1][i - 1] = 1;
41
		}
42
		for (int i = 2; i <= m; i++) {
43
			a[1][i][i + n - 2] = 1;
44
		}
45
		for (int i = 2; i <= n; i++) {
46
			for (int j = 2; j <= m; j++) {
47
				for (int k = 0; k <= n + m - 2; k++) {
48
					a[i][j][k] = (a[i - 1][j][k] + a[i][j - 1][k]) / 2;
49
				}
50
				a[i][j][0] += 1;
51
			}
52
		}
53
		for (int i = 2; i <= n; i++) {
54
			db *g = f[i - 1];
55
			g[i - 1] = 2;
56
			for (int j = 0; j <= n + m - 2; j++) {
57
				g[j] -= a[i - 1][1][j] + a[i][m][j];
58
			}
59
			g[0] = -g[0] + 2;
60
		}
61
		for (int i = 2; i <= m; i++) {
62
			db *g = f[i + n - 2];
63
			g[i + n - 2] = 2;
64
			for (int j = 0; j <= n + m - 2; j++) {
65
				g[j] -= a[1][i - 1][j] + a[n][i][j];
66
			}
67
			g[0] = -g[0] + 2;
68
		}
69
		gauss(f, n + m - 2);
70
		db ans = 0;
71
		for (int i = 1; i <= n + m - 2; i++) {
72
			ans += res[i] * a[x][y][i];
73
		}
74
		ans += a[x][y][0];
75
		return ans;
76
	}
77
};

Maze

「HDU 4035」Maze

题目描述

给定一棵 $n$ 个点的树,一个人在 $1$,每次有 $k_i$ 的概率传送回 $1$,有 $e_i$ 的概率逃出,有 $1 - k_i - e_i$ 的概率随机游走到邻居。问逃出时走过边数的期望。

数据范围:$T \le 30, n \le 10^4$。

思路分析

$f(u)$ 表示从 $u$ 出发经过边数的期望。记 $p_u$ 表示 $u$ 的父亲,$d_u$ 表示 $u$ 的度数,那么有:

更具体地,对于非根结点 $v$,有:

对于根结点 $1$,有:

可以把每个点表示成一个三元组 $(a_i, b_i, c_i)$ 表示 $f(i) = a_if(1) + b_if(p_i) + c_i$,然后在树上自底向上消元。注意对根结点要特判,时间复杂度 $O(n)$。

代码实现

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
typedef double db;
5
const int maxn = 1e4;
6
const db eps = 1e-9; // eps = 1e-8 wa
7
int T, n, d[maxn + 3];
8
db k[maxn + 3], e[maxn + 3], a[maxn + 3], b[maxn + 3], c[maxn + 3], ans;
9
bool flag;
10
vector<int> G[maxn + 3];
11
12
void add(int u, int v) {
13
	G[u].push_back(v);
14
}
15
16
void dfs(int u, int pa = 0) {
17
	for (int i = 0, v; i < G[u].size(); i++) {
18
		v = G[u][i];
19
		if (v == pa) continue;
20
		dfs(v, u);
21
	}
22
	if (u == 1) {
23
		db t = (1 - k[u] - e[u]) / d[u];
24
		db p = 1 - k[u], q = 1 - k[u] - e[u];
25
		for (int i = 0, v; i < G[u].size(); i++) {
26
			v = G[u][i];
27
			if (v == pa) continue;
28
			p -= t * (a[v] + b[v]), q += t * c[v];
29
		}
30
		if (fabs(p) < eps) {
31
			flag = false;
32
		} else {
33
			ans = q / p;
34
		}
35
	} else {
36
		db t = (1 - k[u] - e[u]) / d[u];
37
		a[u] = k[u], b[u] = t, c[u] = 1 - k[u] - e[u];
38
		db p = 1;
39
		for (int i = 0, v; i < G[u].size(); i++) {
40
			v = G[u][i];
41
			if (v == pa) continue;
42
			a[u] += t * a[v], c[u] += t * c[v];
43
			p -= t * b[v];
44
		}
45
		if (fabs(p) < eps) {
46
			flag = false;
47
		} else {
48
			p = 1 / p;
49
			a[u] *= p, b[u] *= p, c[u] *= p;
50
		}
51
	}
52
}
53
54
int main() {
55
	scanf("%d", &T);
56
	for (int _ = 1; _ <= T; _++) {
57
		printf("Case %d: ", _);
58
		scanf("%d", &n);
59
		for (int i = 1; i <= n; i++) {
60
			vector<int>().swap(G[i]), d[i] = 0;
61
		}
62
		for (int i = 1, u, v; i < n; i++) {
63
			scanf("%d %d", &u, &v);
64
			add(u, v), add(v, u), d[u]++, d[v]++;
65
		}
66
		for (int i = 1; i <= n; i++) {
67
			scanf("%lf %lf", &k[i], &e[i]);
68
			k[i] /= 100, e[i] /= 100;
69
		}
70
		flag = true;
71
		dfs(1);
72
		if (flag) {
73
			printf("%.6f\n", ans);
74
		} else {
75
			puts("impossible");
76
		}
77
	}
78
	return 0;
79
}

分手是祝愿

「LOJ 2145」分手是祝愿

题目描述

有 $n$ 个灯泡,给定它们的明暗状态。还有 $n$ 个开关,第 $i$ 个按下后就把编号为它的所有因数的灯泡取反,一个人随机操作开关,直到有一种方案操作 $\le k$ 次把所有灯变暗时采用最优策略,问期望操作次数。

数据范围:$n \le 10^5$。

思路分析

根据灯泡的明暗序列我们可以直接推出最优的操作序列,于是问题就变成给定 01 串,每次选一个位置取反,直到有 $\le k$ 个 1 时停止。记 $f_i$ 表示从 $i$ 个 1 变成 $i - 1$ 个 1 的期望步数,那么有:

干就完了,时间复杂度 $O(n \sqrt n)$。

代码实现

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
const int maxn = 1e5, mod = 1e5 + 3;
5
int n, k, a[maxn + 3], c, f[maxn + 3];
6
7
int qpow(int a, int b) {
8
	int c = 1;
9
	for (; b; b >>= 1, a = 1ll * a * a % mod) {
10
		if (b & 1) c = 1ll * a * c % mod;
11
	}
12
	return c;
13
}
14
15
int main() {
16
	scanf("%d %d", &n, &k);
17
	for (int i = 1; i <= n; i++) {
18
		scanf("%d", &a[i]);
19
	}
20
	for (int i = n; i; i--) if (a[i]) {
21
		c++;
22
		for (int j = 1; j * j < i; j++) {
23
			if (i % j == 0) a[j] ^= 1;
24
		}
25
		for (int j = 1; j * j <= i; j++) {
26
			if (i % j == 0) a[i / j] ^= 1;
27
		}
28
	}
29
	for (int i = 1; i <= k; i++) {
30
		f[i] = i;
31
	}
32
	for (int i = n; i > k; i--) {
33
		f[i] = (1ll * (n - i) * f[i + 1] + n) * qpow(i, mod - 2) % mod;
34
	}
35
	for (int i = k + 1; i <= n; i++) {
36
		f[i] = (f[i - 1] + f[i]) % mod;
37
	}
38
	int ans = f[c];
39
	for (int i = 1; i <= n; i++) {
40
		ans = 1ll * ans * i % mod;
41
	}
42
	printf("%d\n", ans);
43
	return 0;
44
}

歌唱王国

「Luogu 4548」歌唱王国

题目描述

给定长度为 $n$ 的串,有空串 $S$,每次在 $S$ 末尾加入一个 $[1, m]$ 的字符,直到 $S$ 的后缀为给定串时停止。问 $S$ 的期望长度。

数据范围:$T \le 50, n, m \le 10^5$。

思路分析

记 $f(i)$ 表示 $\vert S \vert$ 为 $i$ 的概率,那么 $i < n$ 时 $f(i) = 0$,$i \ge n$ 时有转移方程:

令 $a_i = [s(1, i) = s(n - i + 1, n)], g(i) = \sum_{j = i + 1}^{\infty} f(j)$,那么有 $g(0) = \sum_{i = 1}^{\infty} f(i) = 1$。我们把它代入 $f$ 的转移方程得到:

我们要求的是 $\sum_{i = 1}^{\infty} f(i) \cdot i$,也就是 $\sum_{i = 0}^{\infty} g(i)$。把 $f, g$ 的生成函数记为 $F, G$,那么我们求的就是 $G(1)$。将转移方程改写,得:

用 KMP 求出 $a_i$,直接计算,时间复杂度 $O(n)$。

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
const int maxn = 1e5, mod = 1e4;
5
int T, m, n, p[maxn + 3], a[maxn + 3], nxt[maxn + 3];
6
7
int main() {
8
	scanf("%d %d", &m, &T);
9
	p[0] = 1;
10
	for (int i = 1; i <= maxn; i++) {
11
		p[i] = p[i - 1] * m % mod;
12
	}
13
	while (T --> 0) {
14
		scanf("%d", &n);
15
		for (int i = 1; i <= n; i++) {
16
			scanf("%d", &a[i]);
17
		}
18
		for (int i = 2, k = 0; i <= n; i++) {
19
			while (k && a[k + 1] != a[i]) k = nxt[k];
20
			if (a[k + 1] == a[i]) k++;
21
			nxt[i] = k;
22
		}
23
		int ans = 0;
24
		for (int k = n; k; k = nxt[k]) {
25
			ans += p[k];
26
		}
27
		ans %= mod;
28
		printf("%04d\n", ans);
29
	}
30
	return 0;
31
}

硬币游戏

「Luogu 3706」硬币游戏

题目描述

有 $n$ 个人,每个人有一个长度为 $m$ 的 01 串。在初始为空的串 $S$ 末尾随机加入 0 或 1,直到 $S$ 的后缀是某个 01 串为止,持有该串的人胜利。问最终每个人胜利的概率。

数据范围:$n, m \le 300$。

思路分析

01 串可以分为三类:未终止态,终止态,非法态。记 $P_0$ 表示所有未终止态的概率和,$P_i$ 表示 $i$ 胜利的概率。拿样例举例,$n = m = 3$,三个串为 THT, TTH, HTT,我们考虑 $P_2$。首先随便选一个串后面加上 TTH,概率是 $0.125P_0$。发现有可能提前变成终止态,如果在加第一个字符时变成终止态,原串的一定是 THHT 结尾的,所以概率是 $0.25P_1 + 0.25P_3$。同理,如果在加第二个字符时变成终止态,概率是 $0.5P_3$。所以有 $P_2 = 0.125P_0 - 0.25P_1 - 0.75P_3$。类似地可以列出 $n$ 个方程,最后再外加一个 $\sum_{i = 1}^{n} P_i = 1$,用高斯消元解方程即可。时间复杂度 $O(n^3)$。

代码实现

1
#include <bits/stdc++.h>
2
using namespace std;
3
4
typedef double db;
5
const int maxn = 300, mod = 987898789;
6
int n, m, bin[maxn + 3], a[maxn + 3][maxn + 3];
7
char s[maxn + 3][maxn + 3];
8
db num[maxn + 3], f[maxn + 3][maxn + 3], res[maxn + 3];
9
10
int value(int x, int l, int r) {
11
	return (a[x][r] + 1ll * (mod - a[x][l - 1]) * bin[r - l + 1]) % mod;
12
}
13
14
void gauss(db a[][maxn + 3], int n) {
15
	for (int i = 1; i <= n; i++) {
16
		int id = i;
17
		for (int j = i + 1; j <= n; j++) {
18
			if (fabs(a[j][i]) > fabs(a[id][i])) id = j;
19
		}
20
		if (id != i) {
21
			swap(a[id], a[i]);
22
		}
23
		for (int j = i + 1; j <= n; j++) {
24
			db t = a[j][i] / a[i][i];
25
			for (int k = i; k <= n; k++) {
26
				a[j][k] -= t * a[i][k];
27
			}
28
			a[j][0] -= t * a[i][0];
29
		}
30
	}
31
	for (int i = n; i; i--) {
32
		res[i] = a[i][0] / a[i][i];
33
		for (int j = 1; j < i; j++) {
34
			a[j][0] -= res[i] * a[j][i];
35
		}
36
	}
37
}
38
39
int main() {
40
	scanf("%d %d", &n, &m);
41
	bin[0] = 1, num[0] = 1;
42
	for (int i = 1; i <= m; i++) {
43
		bin[i] = (bin[i - 1] * 2) % mod;
44
		num[i] = num[i - 1] / 2;
45
	}
46
	for (int i = 1; i <= n; i++) {
47
		scanf("%s", s[i] + 1);
48
		for (int j = 1; j <= m; j++) {
49
			a[i][j] = (a[i][j - 1] * 2 + (s[i][j] == 'H')) % mod;
50
		}
51
	}
52
	for (int i = 1; i <= n; i++) {
53
		f[i][n + 1] = -num[m];
54
		for (int j = 1; j <= n; j++) {
55
			for (int k = 1; k <= m; k++) {
56
				if (value(i, 1, k) == value(j, m - k + 1, m)) {
57
					f[i][j] += num[m - k];
58
				}
59
			}
60
		}
61
	}
62
	for (int i = 0; i <= n; i++) {
63
		f[n + 1][i] = 1;
64
	}
65
	gauss(f, n + 1);
66
	for (int i = 1; i <= n; i++) {
67
		printf("%.9lf\n", res[i]);
68
	}
69
	return 0;
70
}