数学专题训练1 概率dp

数学专题训练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=1nE(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(Xi1,j)(1p)+E(Xi1,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(Xi1)(1p)+E(Xi1)p

3. Aeroplane chess

image-20210722221332496

问路径的期望长度,从起点开始搜。

#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

image-20210722230922314 image-20210722230937914

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=14(f(x+dxi,y+dyi)+1)​ 由这个可以列出线性方程组,然后高斯消元求解即可

记住方程组记录的一定是可以走到的点, 只标注能走到的点,不能走到的点不标注,不然会导致矩阵的秩不满从而导致多解. 不仅#走不到,不连通的点也走不到!

无解意味着从起点不能到达终点,否则一定有唯一解。不过怎么证明矩阵的秩等于 n ∗ m n * m nm​ 呢?

#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 1n10。地雷点的坐标范围: [ 1 , 1 0 8 ] [1,10^8] [1,108].

每次有 p p p​ 的概率前进一步, 1 − p 1-p 1p 的概率前进 2 2 2​ 步。问顺利通过这条路的概率。就是不要走到有地雷的地方。

这样每一段只有一个地雷。我们只要求得通过每一段的概率。乘法原理相乘就是答案。对于每一段,通过该段的概率 等于 1-踩到该段终点的地雷的概率。

就比如第一段 1 ∼ x [ 1 ] 1\sim x[1] 1x[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]=1p[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} (fi1fi)(011pp)=(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=KuE1+BuEFu+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 x1 队伍比赛),每个队打败另外一个队都有一个概率。问最后胜利的概率最大的是哪只球队。

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+(02i1)​.

那么就是 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,i1)f(id,i1)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 1N,M105

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 0n1 的坐标轴,给出起点 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 n1 号点),在走的过程中如果到达 0 0 0 号点或 n − 1 n-1 n1 号点,那么下一步往反方向走。每次可以走 1 ∼ m 1\sim m 1m 步,每步概率为 p [ i ] p[i] p[i],问走到终点的期望步数。( n , m , X , Y ≤ 100 n,m,X,Y\le 100 n,m,X,Y100

高斯消元+概率dp,把 n n n 个点变成 2 n − 2 2n-2 2n2 个点,就是 01234321 01234321 01234321 . 然后连边求矩阵然后高斯消元

不过肉眼可见两个坑点。一个是 m > n m > n m>n,可能某个点到达的概率不止 1 m \frac{1}{m} m1,要用 + = += +=;第二个是不是所有点都能到达(比如 m = 1 m=1 m=1​ 但是终点在中间某个位置),高斯消元未知数不能带有走不到的点,不然就不是满秩矩阵.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值