数学专题训练1
1. RollingDiceDivOne
有 n n n 个骰子,第 i i i 个骰子拥有 d i c e [ i ] dice[i] dice[i] 面,每面包含 1 , 2 , . . . , d i c e [ i ] 1,2,...,dice[i] 1,2,...,dice[i] 的点数。同时掷这 n n n 个骰子,问掷出来的各个骰子的总点数是多少的概率最高,如果有多个答案,返回最小的那个。
数据规模:n为 [ 1 , 50 ] [1,50] [1,50],每个骰子的面数取值范围为 [ 1 , 1 0 9 ] [1,10^9] [1,109]
列一个表格找规律便可以发现
- 列表一定是对称的
- 如果列表的长度大于等于骰子的面数,列表中间值一定是极值点。
- 如果以上不成立,那么第一个新极值点数在最终列表中的位置肯定是“当前列表长度-1”。
public class RollingDiceDivOne {
public long mostLikely(int[] dice) {
long sum = 0;
int max = 0;
for (int die : dice) {
sum += die - 1;
max = Math.max(max, die - 1);
}
return dice.length + Math.min(sum / 2, sum - max);
}
}
2. CoinReversing
记住 E ( X + Y ) = E ( X ) + E ( Y ) E(X + Y) = E(X) + E(Y) E(X+Y)=E(X)+E(Y) 不需要满足 X X X 和 Y Y Y 相互独立.
假设第 i i i 次选择的事件记为 X i X_i Xi,则选择第 j j j 个硬币正面朝上记作 X i , j X_{i,j} Xi,j. 则 X i = X i , 1 + X i , 2 + . . . + X i , n X_i = X_{i,1} + X_{i,2} + ... + X_{i,n} Xi=Xi,1+Xi,2+...+Xi,n. 那么 E ( X i ) = ∑ j = 1 n E ( X i , j ) E(X_i) = \sum\limits_{j=1}^{n}E(X_{i,j}) E(Xi)=j=1∑nE(Xi,j). 我们还知道第 i i i 次选择,第 j j j 个硬币被选到的概率是 p = a i n p = \frac{a_i}{n} p=nai. 不妨定义 a 0 = 0 , E ( X 0 ) = n a_0 = 0,E(X_0) = n a0=0,E(X0)=n.
那么, E ( X i , j ) = E ( X i − 1 , j ) ∗ ( 1 − p ) + E ( X i − 1 , j ‾ ) ∗ p E(X_{i,j}) = E(X_{i-1,j}) * (1-p) + E(\overline{X_{i-1,j}}) * p E(Xi,j)=E(Xi−1,j)∗(1−p)+E(Xi−1,j)∗p.
所以, E ( X i ) = E ( X i − 1 ) ∗ ( 1 − p ) + E ( X i − 1 ‾ ) ∗ p E(X_{i}) = E(X_{i-1}) * (1-p) + E(\overline{X_{i-1}}) * p E(Xi)=E(Xi−1)∗(1−p)+E(Xi−1)∗p
3. Aeroplane chess
问路径的期望长度,从起点开始搜。
#include<bits/stdc++.h>
using namespace std;
const int N = 100010;
double f[N];
int n, m;
unordered_map<int, int> mp;
double dp(int x)
{
if(f[x] >= 0) return f[x];
if(x >= n) return 0;
f[x] = 0;
if(mp.count(x)) f[x] += dp(mp[x]);
else for(int i = 1; i <= 6; i++)
{
f[x] += 1.0 / 6 * (dp(x + i) + 1);
}
return f[x];
}
int main()
{
while(cin >> n >> m, n || m)
{
mp.clear();
fill(f, f + N, -1);
//memset(f, -1, sizeof f);
for(int i = 1; i <= m; i++)
{
int x, y;
scanf("%d%d", &x, &y);
mp[x] = y;
}
printf("%.4f\n", dp(0));
}
return 0;
}
4. Card Collector
f [ S ] f[S] f[S] 在等式两边同时出现,因此把 f [ S ] f[S] f[S] 移项到同一侧即可.
#include<bits/stdc++.h>
using namespace std;
const int N = (1 << 20);
double f[N], p[25];
int n;
double dp(int x)
{
if(f[x] >= 0) return f[x];
f[x] = 0;
double P = 0;
bool flag = false;
for(int i = 0; i < n; i++)
{
if(!(x >> i & 1)) P += p[i], flag = true;
}
if(!flag) return f[x] = 0;
f[x] = 1 - P;
for(int i = 0; i < n; i++)
{
if(!(x >> i & 1)) f[x] += (dp(x | (1 << i)) + 1) * p[i];
}
f[x] /= P;
return f[x];
}
int main()
{
while(cin >> n)
{
for(int i = 0; i < n; i++) scanf("%lf", &p[i]);
memset(f, -1, sizeof f);
printf("%.10f\n", dp(0));
}
return 0;
}
5. Bag of mice
求公主获胜概率
分类讨论就可以了,假设当前有 a a a 只白鼠 b b b 只黑鼠,那么分为:公主抓到白鼠;公主抓到黑鼠、龙抓到白鼠;公主抓到黑鼠、龙抓到白鼠、跑了白鼠;公主抓到黑鼠、龙抓到白鼠、跑了黑鼠。共四种情况。注意讨论一下边界.
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
double f[N][N];
int n, m;
double dp(int a, int b)
{
if(a > 0 && b <= 0) return 1;
if(a <= 0) return 0;
if(f[a][b] >= 0) return f[a][b];
f[a][b] = 0;
f[a][b] += 1.0 * a / double(a + b);
double t1 = 0, t2 = 0;
if(b >= 2) t1 = 1.0 * b / double(a + b) * (b - 1) / double(a + b - 1);
if(b >= 3) t2 = (b - 2) / double(a + b - 2) * dp(a, b - 3);
if(b >= 2 && a >= 1) t2 += 1.0 * a / double(a + b - 2) * dp(a - 1, b - 2);
f[a][b] += t1 * t2;
return f[a][b];
}
int main()
{
scanf("%d%d", &n, &m);
memset(f, -1, sizeof f);
printf("%.15f", dp(n, m));
return 0;
}
6. Where is the canteen
题意补充:若从 ( x , y ) (x, y) (x,y) 能往 k k k 个方向走,那么往每个方向走的概率是 1 k \frac{1}{k} k1.
状态之间有环,要用高斯消元
有方程 f ( x , y ) = 1 4 ∑ i = 1 4 ( f ( x + d x i , y + d y i ) + 1 ) f(x, y) = \frac{1}{4}\sum\limits_{i=1}^4(f(x + dx_i, y + dy_i) + 1) f(x,y)=41i=1∑4(f(x+dxi,y+dyi)+1) 由这个可以列出线性方程组,然后高斯消元求解即可
记住方程组记录的一定是可以走到的点, 只标注能走到的点,不能走到的点不标注,不然会导致矩阵的秩不满从而导致多解. 不仅#走不到,不连通的点也走不到!
无解意味着从起点不能到达终点,否则一定有唯一解。不过怎么证明矩阵的秩等于 n ∗ m n * m n∗m 呢?
#include<bits/stdc++.h>
#define x first
#define y second
using namespace std;
const int N = 230;
const double eps = 1e-9;
double f[N][N];
char s[20][20];
int n, m, ID[20][20], cnt[20][20], idx;
int sx, sy;
typedef pair<int, int> P;
int dx[] = {1, -1, 0, 0}, dy[] = {0, 0, 1, -1};
bool bfs()
{
//只标注能走到的点,不能走到的点不标注,不然会导致矩阵的秩不满从而导致多解.
//不仅#走不到,不连通的点也走不到!
idx = 0;
memset(ID, 0, sizeof ID);
memset(cnt, 0, sizeof cnt);
memset(f, 0, sizeof f);
bool flag = false;
queue<P> que;
que.push({sx, sy});
ID[sx][sy] = ++idx;
while(que.size())
{
auto p = que.front(); que.pop();
int x = p.x, y = p.y;
if(s[x][y] == '$')
{
flag = true;
continue;
}
for(int i = 0; i < 4; i++)
{
int nx = x + dx[i], ny = y + dy[i];
if(nx < 1 || nx > n || ny < 1 || ny > m) continue;
if(s[nx][ny] == '#') continue;
cnt[x][y]++;
if(!ID[nx][ny])
{
ID[nx][ny] = ++idx;
que.push({nx, ny});
}
}
}
return flag;
}
double gauss(int n)
{
for(int r = 1, c = 1; c <= n; c++)
{
int t = r;
for(int i = r; i <= n; i++)
{
if(fabs(f[i][c]) > fabs(f[t][c])) t = i;
}
if(fabs(f[t][c]) < eps) continue;
for(int j = c; j <= n + 1; j++)
{
swap(f[r][j], f[t][j]);
}
for(int j = n + 1; j >= c; j--) f[r][j] /= f[r][c];
for(int i = 1; i <= n; i++)
{
if(i == r) continue;
if(fabs(f[i][c]) > eps)
//循环方向别反了
for(int j = n + 1; j >= c; j--) f[i][j] -= f[r][j] * f[i][c];
}
r++;
}
return f[ID[sx][sy]][n + 1];
}
void build_matrix()
{
for(int x = 1; x <= n; x++)
{
for(int y = 1; y <= m; y++)
{
if(!ID[x][y]) continue;
int id = ID[x][y];
f[id][id] = 1;
for(int i = 0; i < 4; i++)
{
int nx = x + dx[i], ny = y + dy[i];
if(!ID[nx][ny]) continue;
//如果 (x,y) 是终点,那么 ID[nx, ny] 可能不为 0 但是 cnt[x][y] 一定是0.
if(cnt[x][y]) f[id][ID[nx][ny]] = -1.0 / cnt[x][y];
}
if(cnt[x][y]) f[id][idx + 1] = 1;
}
}
}
int main()
{
while(cin >> n >> m)
{
for(int i = 1; i <= n; i++)
{
scanf("%s", s[i] + 1);
}
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
if(s[i][j] == '@') sx = i, sy = j;
}
}
if(!bfs())
{
printf("-1\n");
continue;
}
build_matrix();
printf("%.6f\n", gauss(idx));
}
return 0;
}
/*
1 2
@$
2 2
@.
.$
1 3
@#$
2 2
#$
#@
1 4
.#$@
1 4
..$@
*/
7. 3744 – Scout YYF I
题意:在一条不满地雷的路上,你现在的起点在 1 1 1 处。在 n n n 个点处布有地雷, 1 ≤ n ≤ 10 1\le n\le10 1≤n≤10。地雷点的坐标范围: [ 1 , 1 0 8 ] [1,10^8] [1,108].
每次有 p p p 的概率前进一步, 1 − p 1-p 1−p 的概率前进 2 2 2 步。问顺利通过这条路的概率。就是不要走到有地雷的地方。
这样每一段只有一个地雷。我们只要求得通过每一段的概率。乘法原理相乘就是答案。对于每一段,通过该段的概率 等于 1-踩到该段终点的地雷的概率。
就比如第一段 1 ∼ x [ 1 ] 1\sim x[1] 1∼x[1]. 通过该段其实就相当于是到达 x [ 1 ] + 1 x[1]+1 x[1]+1 点。那么 p [ x [ 1 ] + 1 ] = 1 − p [ x [ 1 ] ] p[x[1]+1]=1-p[x[1]] p[x[1]+1]=1−p[x[1]]. 但是这个前提是p[1]=1,即起点的概率等于1.对于后面的段我们也是一样的假设,这样就乘起来就是答案了。
对于每一段的概率的求法可以通过矩阵乘法快速求出来。
(
f
i
−
1
f
i
)
(
0
1
−
p
1
p
)
=
(
f
i
f
i
+
1
)
\begin{pmatrix}f_{i-1} & f_{i}\end{pmatrix}\begin{pmatrix}0 & 1-p \\ 1 & p \end{pmatrix}=\begin{pmatrix}f_{i} & f_{i + 1}\end{pmatrix}
(fi−1fi)(011−pp)=(fifi+1)
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 15;
double m[2][2];
int a[N];
double p;
void mul(double a[], double b[], double c[][2])
{
static double tmp[2];
memset(tmp, 0, sizeof tmp);
for(int i = 0; i < 2; i++)
{
for(int j = 0; j < 2; j++)
{
tmp[i] += b[j] * c[j][i];
}
}
//printf("### %f %f\n", tmp[0], tmp[1]);
memcpy(a, tmp, sizeof tmp);
}
void mul(double a[][2], double b[][2], double c[][2])
{
static double tmp[2][2];
memset(tmp, 0, sizeof tmp);
for(int i = 0; i < 2; i++)
{
for(int j = 0; j < 2; j++)
{
for(int k = 0; k < 2; k++)
{
tmp[i][j] += b[i][k] * c[k][j];
}
}
}
memcpy(a, tmp, sizeof tmp);
}
double calc(int n)
{
//转移矩阵千万别推导错了.
m[0][0] = 0, m[0][1] = 1 - p;
m[1][0] = 1, m[1][1] = p;
double f[2] = {0, 1};
n--;
while(n)
{
if(n & 1) mul(f, f, m);
//printf("*** %f %f\n", f[0], f[1]);
mul(m, m, m);
n >>= 1;
}
return f[1];
}
int main()
{
int n;
while(cin >> n >> p)
{
for(int i = 1; i <= n; i++)
{
scanf("%d", &a[i]);
}
double ans = 1;
//一定要排序,不然快速幂部分就传入负数然后就超时了。
sort(a + 1, a + n + 1);
for(int i = 1; i <= n; i++)
{
ans *= 1 - calc(a[i] - a[i - 1]);
}
printf("%.7f\n", ans);
}
return 0;
}
8. Maze
有n个房间,由n-1条通道连通起来,实际上就形成了一棵树,从结点1出发,开始走,在每个结点i都有3种可能:
1.被杀死,回到结点1处(概率为ki)
2.找到出口,走出迷宫 (概率为ei)
3.和该点相连有m条边,随机走一条
求:走出迷宫所要走的步数的期望值。
好难的一道题
设点 u u u 父结点为 F u F_u Fu,子结点是 C h u Ch_u Chu,则每个结点都可以写为 E u = K u ∗ E 1 + B u ∗ E F u + C u E_u = K_u * E_1 + B_u * E_{F_u} + C_u Eu=Ku∗E1+Bu∗EFu+Cu.
调不出来了,扔到这里
#include<bits/stdc++.h>
using namespace std;
const int N = 10010, M = 2 * N;
int h[N], e[M], ne[M], idx;
int n, dout[N];
double A[N], B[N], C[N], Q[N], E[N], K[N];
const double eps = 1e-8;
inline void add(int a, int b)
{
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
bool dfs(int u, int fa)
{ int cnt = 0;
double sumA = 0, sumB = 0, sumC = 0;
for(int i = h[u]; i != -1; i = ne[i])
{
int v = e[i];
if(v == fa) continue;
if(!dfs(v, u)) return false;
sumA += A[v], sumB += B[v], sumC += C[v];
cnt++;
}
if(!cnt) A[u] = K[u], B[u] = 1 - K[u] - E[u], C[u] = 1 - K[u] - E[u];
else
{
Q[u] = (1 - K[u] - E[u]) / dout[u];
double tmp = 1 - Q[u] * sumB;
if(fabs(tmp) < eps) return false;
A[u] = (K[u] + Q[u] * sumA) / tmp;
B[u] = Q[u] / tmp;
C[u] = Q[u] * (dout[u] + sumC) / tmp;
//printf("*** %d %d %.15f %f %f %f\n", u, fabs(tmp) < eps, tmp, sumB, Q[u], A[u]);
}
return true;
}
int kase;
int main()
{
int T;
scanf("%d", &T);
while(T--)
{
memset(h, -1, sizeof h);
idx = 0;
memset(dout, 0, sizeof dout);
scanf("%d", &n);
for(int i = 1; i < n; i++)
{
int x, y;
scanf("%d%d", &x, &y);
add(x, y), add(y, x);
dout[x]++, dout[y]++;
}
for(int i = 1; i <= n; i++)
{
scanf("%lf%lf", &K[i], &E[i]);
K[i] /= 100, E[i] /= 100;
}
printf("Case %d: ", ++kase);
if(dfs(1, -1) && fabs(A[1] - 1) > eps) printf("%.10f\n", C[1] / (1 - A[1]));
else printf("impossible\n");
//printf("### %f %f %f %f\n", A[1], B[1], C[1], Q[1]);
}
return 0;
}
/*
1
3
1 2
2 3
0 0
100 0
0 100
*/
9. LOOPS
题意:迷宫是一个R*C的布局,每个格子中给出停留在原地,往右走一个,往下走一格的概率,起点在(1,1),终点在(R,C),每走一格消耗两点能量,求出最后所需要的能量期望
入门级概率dp. 题面有两个问题, 一个是没说多组数据(不过话说杭电的题目一般都是多组测试数据);一个是如果到了一个方格,这个方格只能到达自己,那么这点的期望是0.
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
const double eps = 1e-8;
double f[N][N], k1[N][N], k2[N][N], k3[N][N];
int n, m;
double dp(int x, int y)
{
if(f[x][y] >= 0) return f[x][y];
f[x][y] = 0;
if(fabs(1 - k1[x][y]) > eps)
{
f[x][y] += 2 * k1[x][y] / (1 - k1[x][y]);
if(y < m) f[x][y] += double(k2[x][y]) / (1.0 - k1[x][y]) * (dp(x, y + 1) + 2);
if(x < n) f[x][y] += double(k3[x][y]) / (1.0 - k1[x][y]) * (dp(x + 1, y) + 2);
}
return f[x][y];
}
int main()
{
while(cin >> n >> m)
{
memset(f, -1, sizeof f);
f[n][m] = 0;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
scanf("%lf%lf%lf", &k1[i][j], &k2[i][j], &k3[i][j]);
}
}
printf("%.3f\n", dp(1, 1));
}
return 0;
}
10. Check the difficulty of problems
题意:有 t t t 支队伍, m m m道题,问保证每队最少做一题,冠军最少做 n n n 题的概率
和期望没什么关系
11. Football
2 n 2^n 2n 个队进行足球赛淘汰赛(两支相邻的队伍比赛淘汰一只,即第 x x x 只与第 x ⊕ 1 x \oplus 1 x⊕1 队伍比赛),每个队打败另外一个队都有一个概率。问最后胜利的概率最大的是哪只球队。
设 f ( x , i ) f(x, i) f(x,i) 为第 x x x 只队伍在第 i i i 轮胜利的概率。那么在第 i i i 轮和他比赛的队伍为 i d = ( ( x / 2 i ) ⊕ 1 ) ∗ 2 i + ( 0 ∼ 2 i − 1 ) id = ((x /2^i) \oplus 1) * 2^i +(0 \sim 2^i - 1) id=((x/2i)⊕1)∗2i+(0∼2i−1).
那么就是 f ( x , i ) = f ( x , i − 1 ) ∗ ∑ f ( i d , i − 1 ) ∗ g ( x , i d ) f(x, i) = f(x, i - 1) * \sum f(id, i - 1) * g(x,id) f(x,i)=f(x,i−1)∗∑f(id,i−1)∗g(x,id).
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = (1 << 8);
int n;
double g[N][N], f[N][8];
int main()
{
while(cin >> n && n != -1)
{
memset(f, 0, sizeof f);
for(int i = 0; i < (1 << n); i++)
{
for(int j = 0; j < (1 << n); j++)
{
scanf("%lf", &g[i][j]);
}
}
//注意枚举顺序不要错.
for(int i = 0; i < n; i++)
{
for(int x = 0; x < (1 << n); x++)
{
if(i == 0)
{
f[x][i] = g[x][x ^ 1];
}
else
{
int id = ((x >> i) ^ 1) << i;
for(int d = 0; d < (1 << i); d++)
{
f[x][i] += f[id + d][i - 1] * g[x][id + d];
}
f[x][i] *= f[x][i - 1];
}
}
}
int ans = 0;
double res = f[0][n - 1];
for(int i = 0; i < (1 << n); i++)
{
if(res < f[i][n - 1]) res = f[i][n - 1], ans = i;
}
printf("%d\n", ans + 1);
}
return 0;
}
12. Kids and Prizes
题意: N N N 个礼品箱, 每个礼品箱内的礼品只有第一个抽到的人能拿到. M M M 个小孩每个人依次随机抽取一个, 求送出礼品数量的期望值. 1 ≤ N , M ≤ 1 0 5 1 ≤ N, M ≤ 10^5 1≤N,M≤105
m个人是独立的。
对于每个礼物不被人选中的概率为((n-1)/n)^m
那么不被选中的礼物数的期望就是 n*((n-1)/n)^m (考虑二项分布)
所以答案就是 n-n*((n-1)/n)^m;
13. Time travel
题意:一个 0 ∼ n − 1 0\sim n-1 0∼n−1 的坐标轴,给出起点 X X X、终点 Y Y Y,和初始方向 D D D( 0 0 0 表示从左向右、 1 1 1 表示从右向左, − 1 -1 −1 表示 0 0 0 号点或 n − 1 n-1 n−1 号点),在走的过程中如果到达 0 0 0 号点或 n − 1 n-1 n−1 号点,那么下一步往反方向走。每次可以走 1 ∼ m 1\sim m 1∼m 步,每步概率为 p [ i ] p[i] p[i],问走到终点的期望步数。( n , m , X , Y ≤ 100 n,m,X,Y\le 100 n,m,X,Y≤100)
高斯消元+概率dp,把 n n n 个点变成 2 n − 2 2n-2 2n−2 个点,就是 01234321 01234321 01234321 . 然后连边求矩阵然后高斯消元
不过肉眼可见两个坑点。一个是 m > n m > n m>n,可能某个点到达的概率不止 1 m \frac{1}{m} m1,要用 + = += +=;第二个是不是所有点都能到达(比如 m = 1 m=1 m=1 但是终点在中间某个位置),高斯消元未知数不能带有走不到的点,不然就不是满秩矩阵.