概率DP练习

8 篇文章 0 订阅
6 篇文章 0 订阅

LightOJ - 1274
题意等价于:由x个1,y个0组成的全排列01字符串
某个位置与前一个位置不同的数量期望,第一个的前面一个数默认位1

思路:先求出x和y
dp[i][j][k]中表示第i个位置位j的,且前面i个数中有k个1的状态到达终点的期望值

答案为dp[0][1][0]

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>

using namespace std;
typedef long long ll;
const int N = 5010;
double dp[2][2][N], p1, p2;//     k表示yes的数量
int t, n, s, yes, no, l, r, cas;

int main() {
    scanf("%d", &t);
    while (t--) {
        memset(dp, 0, sizeof dp);
        scanf("%d%d", &n, &s);
        yes = s - 2 * n, no = n - yes;
        for (int i = n - 1; i >= 0; i--) {
            l = max(0, i - no), r = min(i, yes);
            for (int j = l; j <= r; j++)//枚举yes的数量
            {
                p1 = 1.0 * (no - i + j) / (n - i), p2 = 1.0 * (yes - j) / (n - i);
                dp[i & 1][0][j] = dp[!(i & 1)][0][j] * p1 + (dp[!(i & 1)][1][j + 1] + 1) * p2;
                dp[i & 1][1][j] = (dp[!(i & 1)][0][j] + 1) * p1 + dp[!(i & 1)][1][j + 1] * p2;
            }
        }
        printf("Case %d: %.7lf\n", ++cas, dp[0][1][0]);
    }
    return 0;
}

LightOJ - 1287
题意:

给你n(n<15)个点,m条无向边及其权值,你是一个刚偷完东西的小偷,身后有警察追你,因此你需要从1号点开始进行逃亡,在每个点你都有≥1个选择:

1、停留在原地5分钟

2、假设你在u号点,与u节点直接相连的点有v1,v2,…,vx号点,如果其中的某些节点到达它后可以通过这个节点遍历所有的点,那么你就可以选择这个点。选择每个合法点的概率是相等的,求你无路可走的时间期望

思路:

1、对于每个点u(编号0~n-1), 先找出所有可选择的点,假设有k个可选点

2、dp[zt][u]表示经过的点的状态为zt,目前在u点时走完所有点的时间期望(v为u的可选点)


在这里插入图片描述

化简可得:
在这里插入图片描述
3、至于能否走完所有的点,做一下记忆化搜索就可以了

#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
typedef pair<int, int> pii;
const int N = 1 << 16;
int t, n, m, a, b, w, cas;
vector<pii> v[20];
bool vis[N][20], f[N][20];
double dp[N][20];
bool dfs(int st, int u) {
    if (st + 1 == 1 << n) {
        dp[st][u] = 0;
        return 1;
    }
    if (vis[st][u])return f[st][u];
    vis[st][u] = 1;
    double &x = dp[st][u] = 0;
    int cnt = 0;
    for (auto i:v[u]) {
        if (!(st & (1 << i.first)) && dfs(st | (1 << i.first), i.first)) {
            cnt++;
            x += dp[st | 1 << i.first][i.first] + i.second;
            f[st][u] = 1;
        }
    }
    if (!f[st][u])return 0;
    x = (x + 5) / cnt;
    return 1;
}
int main() {
    scanf("%d", &t);
    while (t--) {
        memset(dp, 0, sizeof dp);
        memset(vis, 0, sizeof vis);
        memset(f, 0, sizeof f);
        for (int i = 0; i < 18; i++)v[i].clear();
        scanf("%d%d", &n, &m);
        while (m--) {
            scanf("%d%d%d", &a, &b, &w);
            v[a].push_back({b, w});
            v[b].push_back({a, w});
        }
        dfs(1, 0);
        printf("Case %d: %.10lf\n", ++cas, dp[1][0]);
    }
    return 0;
}

牛客练习赛82D
题意等价于:有n个东西,每次选择全部的东西,每个东西有p/q的概率消失
每次需要花费f[n-i+1],求n个东西全部消失的花费期望

F [ i ] = f [ n − i + 1 ] + ∑ k = 0 i C i k ∗ F [ i − k ] ∗ ( p / q ) k ∗ ( 1 − p / q ) i − k F[i]=f[n-i+1]+\sum_{k=0}^iC_i^k*F[i-k]*(p/q)^k*(1-p/q)^{i-k} F[i]=f[ni+1]+k=0iCikF[ik](p/q)k(1p/q)ik

移项得 ( 1 − ( 1 − p / q ) i − k ) ∗ F [ i ] = ∑ k = 1 i C i k ∗ F [ i − k ] ∗ ( p / q ) k ∗ ( 1 − p / q ) i − k (1-(1-p/q)^{i-k})*F[i]=\sum_{k=1}^iC_i^k*F[i-k]*(p/q)^k*(1-p/q)^{i-k} (1(1p/q)ik)F[i]=k=1iCikF[ik](p/q)k(1p/q)ik

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;
const int N = 2010, mod = 998244353;
int n;
ll f[N], F[N], c[N][N], a[N], b[N], p, q;
ll qkpow(ll a, ll b) {
    ll sum = 1;
    while (b) {
        if (b & 1)sum = sum * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return sum;
}
void init(int n) {
    f[1] = 1;
    for (int i = 2; i <= n; i++)f[i] = (f[i - 1] + f[i - 2]) % mod;
    for (int i = 0; i <= n; i++)
        for (int j = 0; j <= i; j++)
            if (!j)c[i][j] = 1;
            else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
    a[0] = b[0] = 1;
    a[1] = p * qkpow(q, mod - 2) % mod, b[1] = (1 - a[1] + mod) % mod;
    for (int i = 2; i <= n; i++)a[i] = a[i - 1] * a[1] % mod, b[i] = b[i - 1] * b[1] % mod;
}
int main() {
    scanf("%d%lld%lld", &n, &p, &q);
    if (p == q)return puts("1"), 0;
    init(n);
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= i; j++)
            F[i] = (F[i] + c[i][j] * a[j] % mod * b[i - j] % mod * F[i - j]) % mod;
        F[i] = (F[i] + f[n - i + 1]) % mod * qkpow((1 - b[i] + mod) % mod, mod - 2) % mod;
    }
    return printf("%lld\n", F[n]), 0;
}

LightOJ - 1321

题意:有个图,节点编号从0到n-1,每条边都有一个概率p表示能成功通过这条边得概率
一个人从0号点开始往n-1号点走,一旦失败就要回到0号点,每次到0号点需要交2k得钱
一共要走通s次
求最终得期望花费

思路:用spfa选一条概率最大的路径走
设走通一次的期望花费为E
E = p ∗ 2 ∗ k + ( 1 − p ) ∗ ( E + 2 ∗ k ) E=p*2*k+(1-p)*(E+2*k) E=p2k+(1p)(E+2k)
化简得 E = 2 ∗ k / p E=2*k/p E=2k/p

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
typedef pair<int, double> pii;
double d[110];
vector<pii> v[110];
bool vis[110];
int t, n, m, a, b, c, k, s, cas;
double spfa() {
    memset(d, 0, sizeof d);
    memset(vis, 0, sizeof vis);
    queue<int> q;
    q.push(0);
    d[0] = 1;
    vis[0] = 1;
    while (q.size()) {
        int x = q.front();
        q.pop();
        vis[x] = 0;
        for (auto i:v[x]) {
            if (d[i.first] < d[x] * i.second) {
                d[i.first] = d[x] * i.second;
                if (!vis[i.first])q.push(i.first), vis[i.first] = 1;
            }
        }
    }
    return d[n - 1];
}
int main() {
    cin >> t;
    while (t--) {
        cin >> n >> m >> s >> k;
        for (int i = 0; i < n; i++)v[i].clear();
        while (m--) {
            cin >> a >> b >> c;
            v[a].push_back({b, c / 100.0});
            v[b].push_back({a, c / 100.0});
        }
        printf("Case %d: %.10lf\n", ++cas, 2ll * k * s / spfa());
    }
    return 0;
}

LightOJ - 1342
题意:有n根木棍,每根木棍有花费,有些是可识别得,剩下的是不可识别的,每次等概率取出一个,如果这个是可识别的,那么不放回,如果是不可识别的就放回,直到每根木棍都至少拿到过一次后的花费期望
dp[i][j]表示已经拿过了i个不可识别的和j个可识别的最小花费
sum表示还剩下多少根木棍,cnt1,cnt2表示这2种木棍的数量,avg1,avg2表示拿出这2
种木棍的期望花费(也就是平均值,因为每根木棍等概率被拿)
d p [ i ] [ j ] = ( c n t 2 − i ) / s u m ∗ ( d p [ i + 1 ] [ j ] + a v g 2 ) + ( c n t 11 − j ) s u m ∗ ( d p [ i ] [ j + 1 ] + a v g 1 ) + i / s u m ∗ ( d p [ i ] [ j ] + a v g 2 ) dp[i][j]=(cnt2-i)/sum*(dp[i+1][j]+avg2)+(cnt11-j)sum*(dp[i][j+1]+avg1)+i/sum*(dp[i][j]+avg2) dp[i][j]=(cnt2i)/sum(dp[i+1][j]+avg2)+(cnt11j)sum(dp[i][j+1]+avg1)+i/sum(dp[i][j]+avg2)

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>

using namespace std;
int t, n, a, b, cas;
double dp[2][5010];

int main() {
    scanf("%d", &t);
    while (t--) {
        memset(dp, 0, sizeof dp);
        scanf("%d", &n);
        int val1 = 0, val2 = 0, cnt1 = 0, cnt2 = 0;
        for (int i = 1; i <= n; i++) {
            scanf("%d%d", &a, &b);
            if (b == 1)cnt1++, val1 += a;
            else cnt2++, val2 += a;
        }
        double avg1 = 0, avg2 = 0;
        if (cnt1)avg1 = 1.0 * val1 / cnt1;
        if (cnt2)avg2 = 1.0 * val2 / cnt2;
        for (int i = cnt2; i >= 0; i--)
            for (int j = cnt1; j >= 0; j--)
                if (i != cnt2 || j != cnt1) {
                    dp[i & 1][j] = dp[!(i & 1)][j] * (cnt2 - i) / (n - j) +
                                       (dp[i & 1][j + 1] + avg1) * (cnt1 - j) / (n - j) +
                                       avg2 * cnt2 / (n - j);
                    dp[i & 1][j] *= 1.0 * (n - j) / (n - i - j);
                }

        printf("Case %d: %.10lf\n", ++cas, dp[0][0]);
    }
    return 0;
}

LightOJ - 1030
题意:有个1*n的网格,1为起点,n为终点,每个位置都有若干的金币
每次投一个1~6均匀分布的骰子,投到多少前进多少步,并获得那个位置的金币,若此次行走会超过终点,那么重新投,直到刚好走到n位置,求获得金币的期望

d p [ i ] = 1 / 6 ∗ ( ∑ j = i + 1 i + 6 d p [ j ] + a [ j ] ) dp[i]=1/6*(\sum_{j=i+1}^{i+6}dp[j]+a[j]) dp[i]=1/6(j=i+1i+6dp[j]+a[j])

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
int t, n, a[110], cas;
double dp[110];
int main() {
    scanf("%d", &t);
    while (t--) {
        memset(dp, 0, sizeof dp);
        scanf("%d", &n);
        for (int i = 1; i <= n; i++)scanf("%d", &a[i]);
        for (int i = n - 1; i >= 1; i--) {
            int cnt = 0;
            for (int j = i + 1; j <= min(n, i + 6); j++) {
                cnt++;
                dp[i] += dp[j] + a[j];
            }
            dp[i] /= cnt;
        }
        printf("Case %d: %.10lf\n", ++cas, dp[1] + a[1]);
    }
    return 0;
}

LightOJ - 1364
题意:有54张扑克牌其中2张小丑,黑桃、红桃、梅花和方块各13张,每次拿走一张,若拿到小丑,随机将小丑变成黑桃、红桃、梅花或者方块中的一张,直到拿到手的牌中黑桃的数量大于等于A,红桃的数量大于等于B,梅花的数量大于等于C,方块的数量大于等于D时停止,求拿牌的期望数量

a,b,c,d表示黑桃、红桃、梅花和方块的数量,x表示其中一张小丑的状态y表示另一张小丑的状态
0~3表示黑桃、红桃、梅花和方块,4表示还未拿到手

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;
const int inf = 0x3f3f3f3f;
int t, A, B, C, D, cas;
double dp[14][14][14][14][5][5];

double dfs(int a, int b, int c, int d, int x, int y) {
    double &v = dp[a][b][c][d][x][y];
    if (v >= 0)return v;
    int aa = a + (x == 0) + (y == 0);
    int bb = b + (x == 1) + (y == 1);
    int cc = c + (x == 2) + (y == 2);
    int dd = d + (x == 3) + (y == 3);
    if (aa >= A && bb >= B && cc >= C && dd >= D)return v = 0;
    int sum = aa + bb + cc + dd;
    if (sum >= 54)return v = inf;
    v = 1;
    if (a < 13)v += 1.0 * (13 - a) / (54 - sum) * dfs(a + 1, b, c, d, x, y);
    if (b < 13)v += 1.0 * (13 - b) / (54 - sum) * dfs(a, b + 1, c, d, x, y);
    if (c < 13)v += 1.0 * (13 - c) / (54 - sum) * dfs(a, b, c + 1, d, x, y);
    if (d < 13)v += 1.0 * (13 - d) / (54 - sum) * dfs(a, b, c, d + 1, x, y);
    if (x == 4) {
        double t = inf;
        for (int i = 0; i < 4; i++)
            t = min(t, dfs(a, b, c, d, i, y) / (54 - sum));
        v += t;
    }
    if (y == 4) {
        double t = inf;
        for (int i = 0; i < 4; i++)
            t = min(t, dfs(a, b, c, d, x, i) / (54 - sum));
        v += t;
    }
    return v;
}

int main() {
    cin >> t;
    while (t--) {
        memset(dp, -1, sizeof dp);//nan
        cin >> A >> B >> C >> D;
        double ans = dfs(0, 0, 0, 0, 4, 4);
        printf("Case %d: ", ++cas);
        if (ans >= inf)puts("-1");
        else printf("%.10lf\n", ans);
    }
    return 0;
}

LightOJ - 1395
题意:有n扇门,每个门有一个花费,若为正数表示结束游戏,最多能记住k扇门,使得选择门时不考虑他们,求结束游戏的期望花费
f[i]表示记住了i扇门的期望
cnta表示正数的个数,cntb表示负数的个数
avga表示正数门的均值,abgb表示负数门的均值
先对 k = m i n ( k , c n t b ) k=min(k,cntb) k=min(k,cntb)
终点为 f [ k ] = ( c n t b − k ) / ( n − k ) ∗ ( f [ k ] + a v g b ) + a / ( n − k ) ∗ a v g a f[k]=(cntb-k)/(n-k)*(f[k]+avgb)+a/(n-k)*avga f[k]=(cntbk)/(nk)(f[k]+avgb)+a/(nk)avga
中间过程为 f [ i ] = ( b − i ) / ( n − i ) ∗ ( f [ i + 1 ] + a v g b ) + a / ( n − i ) ∗ a v g a f[i]=(b-i)/(n-i)*(f[i+1]+avgb)+a/(n-i)*avga f[i]=(bi)/(ni)(f[i+1]+avgb)+a/(ni)avga
在化简即可

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>

using namespace std;
int t, n, k, cas, x;
double dp[110];

int main() {
    scanf("%d", &t);
    while (t--) {
        int cnta = 0, cntb = 0, suma = 0, sumb = 0;
        memset(dp, 0, sizeof dp);
        scanf("%d%d", &n, &k);
        for (int i = 1; i <= n; i++) {
            scanf("%d", &x);
            if (x > 0)suma += x, cnta++;
            else sumb -= x, cntb++;
        }
        double avga = 0, avgb = 0;
        printf("Case %d: ", ++cas);
        if (cnta)avga = 1.0 * suma / cnta;
        else {
            puts("-1");
            continue;
        }
        if (cntb)avgb = 1.0 * sumb / cntb;
        k = min(k, cntb);
        dp[k] = avgb * (cntb - k) / (n - cntb) + avga * cnta / (n - cntb);
        for (int i = k - 1; i >= 0; i--)
            dp[i] = (dp[i + 1] + avgb) * (cntb - i) / (n - i) + avga * cnta / (n - i);
        printf("%.10lf\n", dp[0]);
    }
    return 0;
}

1543C Need for Pink Slips

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<cmath>

using namespace std;
const double eps = 1e-7;
int t;
double c, m, p, v;

double dp(double c, double m, double p) {
    double res = 0;
    if (c > eps) {
        double x = min(c, v);
        if (m > eps && p > eps)res += c * (dp(c - x, m + x / 2, p + x / 2) + 1);
        else if (m > eps)res += c * (dp(c - x, m + x, p) + 1);
        else res += c * (dp(c - x, m, p + x) + 1);
    }
    if (m > eps) {
        double x = min(m, v);
        if (c > eps && p > eps)res += m * (dp(c + x / 2, m - x, p + x / 2) + 1);
        else if (c > eps)res += m * (dp(c + x, m - x, p) + 1);
        else res += m * (dp(c, m - x, p + x) + 1);
    }
    return res;
}

int main() {
    scanf("%d", &t);
    while (t--) {
        scanf("%lf%lf%lf%lf", &c, &m, &p, &v);
        printf("%.10lf\n", 1.0 + dp(c, m, p));
    }
    return 0;
}

LightOJ - 1408
题意:投丢的概率为p,投中的概率为1-p
若连续投中k1个或连续投丢k2个游戏结束
求投球次数期望
f[i]: 已经连续投中 i 次,距离游戏结束还需投球次数的期望.

g[i]:已经连续投空 i 次,距离游戏结束还需投球次数的期望.

易得转移方程:

f [ i ] = ( 1 − p ) ∗ f [ i + 1 ] + p ∗ g [ 1 ] + 1. f[i] = (1 - p) * f[i + 1] + p * g[1] + 1. f[i]=(1p)f[i+1]+pg[1]+1.

g [ i ] = p ∗ g [ i + 1 ] + ( 1 − p ) ∗ f [ 1 ] + 1. g[i] = p * g[i + 1] + (1 - p) * f[1] + 1. g[i]=pg[i+1]+(1p)f[1]+1.

经简单迭代可得:
在这里插入图片描述
在这里插入图片描述
联立两式可得:
在这里插入图片描述
解出 f[1] 后回带即可得到 g[1].

最终答案 ans = qf[1] + pg[1] + 1.

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<cmath>

using namespace std;
const double eps = 1e-7;
int t, k1, k2, cas;
double p, f, g;

double qkpow(double a, int b) {
    double sum = 1;
    while (b) {
        if (b & 1)sum *= a;
        a *= a;
        b >>= 1;
    }
    return sum;
}

int main() {
    scanf("%d", &t);
    while (t--) {
        scanf("%lf%d%d", &p, &k1, &k2);
        double q = 1 - p;
        printf("Case %d: ", ++cas);
        if (p < eps) {
            if (k2 == 0)printf("0\n");
            else printf("%.10lf\n", 1.0 * k1);
            continue;
        }
        if (q < eps) {
            if (k1 == 0)printf("0\n");
            else printf("%.10lf\n", 1.0 * k2);
            continue;
        }
        double qk = qkpow(q, k1 - 1), pk = qkpow(p, k2 - 1);
        f = (1 - qk) * ((1 - pk) / q + 1 / p) / (1 - (1 - pk) * (1 - qk));
        g = ((1 - p) * f + 1) * (1 - pk) / q;
        printf("%.10lf\n", q * f + p * g + 1);
    }
    return 0;
}
  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值