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 |
|
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
题目描述
给定一棵 $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 |
|
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 | } |
分手是祝愿
题目描述
有 $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 |
|
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 | } |
歌唱王国
题目描述
给定长度为 $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 |
|
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 | } |
硬币游戏
题目描述
有 $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$。发现有可能提前变成终止态,如果在加第一个字符时变成终止态,原串的一定是 TH
,HT
结尾的,所以概率是 $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 |
|
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 | } |