Loading

牛客题单__动态规划课程概率dp例题

牛客题单__动态规划课程概率dp例题

NC15532 Happy Running

大意:

问围着一个x的跑道跑顺时针跑,打卡完毕的时候跑k米及以上的概率。两个打卡点是随机的,必须先打第一个再打第二个,如果先跑到第二个,那么也要先打完第一个点然后再绕一圈到第二个点打卡

思路:

当k<=x时,如果两个点都在前k米,并且第一个点在第二个点前面,那么不行

所以概率为0.5+0.5 * (1-两个都在前k米的概率)

当k大于2x时,必然跑不到

介于x到2x之间时,需要第2个点在第1个点前面,同时两个点都不能在前(2*k-x)米

#include <bits/stdc++.h>
using namespace std;

int t;
double k, x;

int main(void) {
    cin >> t;
    while (t--) {
        cin >> k >> x;
        double res = 0;
        if (k <= x) {
            res = 0.5;
            res += 1.0 * (1.0 * (x - k) / x) * (1.0 * (k + x) / 2 / x);
        } else if (k <= 2 * x) {
            res += 1.0 * (1.0 * (2 * x - k) / x) * (1.0 * (2 * x - k) / 2 / x);
        }
        printf("%.2lf\n", res);
    }
    return 0;
}

POJ2096 Collecting Bugs

大意:

现在有很多bug,按照位置分可以分为n类,按照类型分可以分为m类,现在有一个人,每天可以找到一个bug,问找到全部n个位置m个类型至少各有一个bug,花费天数的期望

思路:

图片说明

但是\(f[i][j]\)等式右边也有\(f[i][j]\),不好转移,所以将其移项到左边,化简一下即可转移了

写的时候大爆搜即可

#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <queue>
#include <string>
using namespace std;

const int N = 1e3 + 5;
typedef long long LL;
double f[N][N];
int n, m;

double dp(int x, int y) {
    if (f[x][y] != 0) return f[x][y];
    if (x == n && y == m) return 0;
    if (x > n || y > m) return 0;
    double p1 = ((1.0 * x / n )* (1.0 * y / m));
    double p2 = ((1.0 * x / n )* (1.0 - 1.0 * y / m));
    double p3 = ((1.0 - 1.0 * x / n) * 1.0 * y / m);
    double p4 = ((1.0 - 1.0 * x / n) * (1.0 - 1.0 * y / m));
    return f[x][y] = (p1 + p2 * (dp(x, y + 1) + 1.0) + p3 * (dp(x + 1, y) + 1.0) +
                      p4 * (dp(x + 1, y + 1) + 1.0)) /
                     (1.0 - p1);
}

int main() {
    memset(f, 0, sizeof f);
    cin >> n >> m;
    printf("%.4f\n", dp(0, 0));
    return 0;
}

NC210477 带富翁

大意:

小明在玩一款带富翁游戏,这个游戏具体来说就是有n个奖励点,每个奖励点有一定的奖励分。一开始他站在位置1。每次他都会扔一个有6面的筛子,如果扔到了x,并且小明现在站在i这个位置,小明就会向前进x步到达i+x这个位置。

如果小明扔出了x并且i+x已经大于了n,那么小明就会重新扔,直到i+x在合适的位置。小明最后如果到达了n号位置,那么小明就会结束游戏,现在小明想知道自己期望的得分是多少。

思路:

还是大爆搜就行,注意如果当前位置和n之间的距离小于6,那么转移就不是1/6的概率了,而是1/(n-now)

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 5;
typedef long long LL;
double a[N], f[N];
int n;

double dp(int now) {
    if (f[now] != 0) return f[now];
    if (now > n) return 0;
    if (now == n) return a[now];
    double res = 0;
    int cnt = min(n - now,6);
    for (int i = 1; i <= cnt; i++) {
        res += dp(now + i);
    }
    return f[now] = res*(1.0 / cnt)+a[now];
}

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    printf("%.8lf", dp(1));
    return 0;
}

NC210481 筛子游戏

大意:

一开始你会得到三个筛子,三个筛子分别有k1,k2,k3面,也就是说分别可以扔出[1,k1],[1,k2],[1,k3]之间数。
一开始的分数为0,每次扔筛子都会扔出x,y,z三个数,但是这个游戏的特别之处在于每次开局都会给定三个数a,b,c如果满足x=a,y=b,z=c那么你的分数就会清零,否则你的分数就会加上x+y+z。现在吉吉国王想知道需要扔多少次才能使得他的分数大于n。

思路:

\(f[now]\)为当前分数为now时,需要扔多少次,那么\(f[now]\)可以转移到\(f[now+k]\)的位置,也可以转移到\(f[0]\):

\(f[i]=∑(p[k]*f[i+k])+f[0]*p[0]+1\)

其中\(p[k]\)为转移的概率,可以看到\(f[i]\)都和\(f[0]\)有关,但是所求即为\(f[0]\),没法直接递推求解,可以先将\(f[0]\)设置为一个常数:

\(f[i]=A[i]*f[0]+B[i]\),将其代入到上式右侧:

\(f[i]=∑(p[k]*A[i+k]*f[0]+p[k]*B[i+k])+f[0]*p[0]+1\)

化简:

\(f[i]=(∑(p[k]*A[i+k])+p[0])f[0]+∑(p[k]*B[i+k])+1\)

所以:

\(A[i]=(∑(p[k]*A[i+k])+p[0]),B[i]=∑(p[k]*B[i+k])+1\)

所以首先打表求出\(p[k]\),然后递推求出A和B数组,最后通过\(f[0]=B[0]/(1-A[0])\)来求解出\(f[0]\)即可

#include <bits/stdc++.h>
using namespace std;
double p[20];
double A[550], B[550];
int main() {
    int n, k1, k2, k3, a, b, c;
    scanf("%d%d%d%d%d%d%d", &n, &k1, &k2, &k3, &a, &b, &c);
    p[0] = 1.0 / k1 / k2 / k3;
    for (int i = 3; i <= k1 + k2 + k3; i++) {
        int cnt = 0;
        for (int j = 1; j <= k1; j++)  //第一个骰子
        {
            for (int k = 1; k <= k2; k++)  //第二个骰子
            {
                int l = i - j - k;
                if (l < 1 || l > k3) continue;
                if (j == a && k == b && l == c) {
                    //如果会跳到0,不统计
                } else if (j + k + l == i)  //三个骰子之和等于i
                {
                    cnt++;
                }
            }
        }
        p[i] = 1.0 * cnt / k1 / k2 / k3;
    }
    for (int i = n; i >= 0; i--)  //递推
    {
        for (int k = 3; k <= k1 + k2 + k3; k++)  //枚举骰子点数总和
        {
            A[i] += p[k] * A[i + k];
            B[i] += p[k] * B[i + k];
        }
        A[i] += p[0];
        B[i] += 1.0;
    }
    double ans = B[0] / (1 - A[0]);
    printf("%.10lf\n", ans);
}

NC210487 食堂

大意:

食堂排队可以看成一个长度为n的队列,一开始吉吉国王站在m这个位置上,一般来说,窗口前的第一个人在打饭的时候会发生四种情况。
第一种情况是打饭的时候窗口没人,这个时候要等待一会儿,发生的概率是p1。
第二种情况是发现自己没带饭卡,这个时候就要回去拿饭卡并且排到了队列的末尾,发生的概率是p2。(这里认为每个人只有在即将打饭的时候才会去摸饭卡,只有这时才有发现自己没带饭卡的机会。)
第三种情况是打饭成功,这个时候队列的长度减一,发生的概率是p3。
第四种情况是食堂关门,这个时候大家都不能打饭了,发生的概率是p4。
吉吉国王老倒霉蛋了,经常在食堂关门的时候排在队伍的前面,因此他想知道这样的事件发生的概率。现在你需要告诉吉吉国王在食堂关门时他排在队伍的前k位的概率。

思路:

图片.png 图片.png
#include <bits/stdc++.h>
using namespace std;
double dp[2010][2010];
double c[2010], p[2010];
#define eps 1e-10

int main() {
    int n, m, k;
    double p1, p2, p3, p4;
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> m >> k >> p1 >> p2 >> p3 >> p4;
    if (p4 < eps) {
        cout << "0.00000\n";
        return 0;
    }
    double p12 = p2 / (1 - p1);
    double p13 = p3 / (1 - p1);
    double p14 = p4 / (1 - p1);
    p[0] = 1;
    for (int i = 1; i <= n; i++) {
        p[i] = p[i - 1] * p12;
    }
    dp[1][1] = p4 / (1 - p1 - p2);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= i; j++) {
            if (j <= k) {
                c[j] = dp[i - 1][j - 1] * p13 + p14;
            } else
                c[j] = dp[i - 1][j - 1] * p13;
        }
        double temp = 0;
        for (int j = 1; j <= i; j++) {
            temp += p[i - j] * c[j];
        }
        dp[i][i] = temp / (1 - p[i]);
        dp[i][1] = p12 * dp[i][i] + p14;
        for (int j = 2; j < i; j++) {
            dp[i][j] = dp[i][j - 1] * p12 + c[j];
        }
    }
    cout << fixed << setprecision(5) << dp[n][m];
    return 0;
}
posted @ 2021-03-01 20:09  WinterFa1L  阅读(147)  评论(0编辑  收藏  举报