把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

概率期望dp 复习笔记

Sushi

给你 \(n(1\leq n\leq 300)\) 碗寿司,每碗寿司有 \(a_i(1\leq a_i\leq 3)\) 个,每次进行如下操作:

  • 等概率地得到一个 \(i\),然后如果当前的 \(a_i>0\),就让 \(a_i-1\),否则就不需要做任何事。

求全部吃完的期望操作次数。

题目分析

注意到 \(a_i\in[1,3]\),故设 \(f_{i,j,k}\) 表示现在碗里只有 \(1\) 个的有 \(i\) 碗,只有 \(2\) 个的有 \(j\) 碗,只有 \(3\) 个的有 \(k\) 碗。

我们发现从 \(f_{x,y,z}\) 推到 \(f_{0,0,0}\) 是复杂的。

不妨将整个过程反过来变成生产寿司从 \(f_{0,0,0}\) 推到 \(f_{x,y,z}\) 即可。

那么我们有转移:

\[f_{i,j,k}=\frac{n-i-j-k}{n}(f_{i,j,k}+1)+\frac{i}{n}(f_{i-1,j,k}+1)+\frac{j}{n}(f_{i+1,j-1,k}+1)+\frac{k}{n}(f_{i,j + 1,k-1}+1) \]

化简有:

\[(i+j+k)f_{i,j,k}=\frac{i}{n}f_{i-1,j,k}+\frac{j}{n}f_{i+1,j-1,k}+\frac{k}{n}f_{i,j+1,k-1}+n \]

最后再除过去就可以了,于是你写出了以下的代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stdlib.h>
#include <vector>
#include <iomanip>
#define int long long
#define N 305
using namespace std;
int n,a[N];
double f[N][N][N];
int sum1,sum2,sum3;
signed main(){
    cin >> n;
    for (int i = 1;i <= n;i ++) scanf("%lld",&a[i]);
    f[0][0][0] = 0.0;
    for (int i = 1;i <= n;i ++) sum1 += (a[i] == 1),sum2 += (a[i] == 2),sum3 += (a[i] == 3);
    for (int i = 0;i <= n;i ++)
        for (int j = 0;j <= n;j ++)
            for (int k = 0 + (i == j && j == 0);i + j + k <= n;k ++) {
                if (i) f[i][j][k] = f[i - 1][j][k] * i + f[i][j][k];
                if (j) f[i][j][k] = f[i + 1][j - 1][k] * j + f[i][j][k];
                if (k) f[i][j][k] = f[i][j + 1][k - 1] * k + f[i][j][k];
                f[i][j][k] = (f[i][j][k] + n) / (i + j + k);
            }
    cout << fixed << setprecision(10) << f[sum1][sum2][sum3];
    return 0;
}

这是错的,因为在转移 \(j\) 可行的时候,\(f_{i+1,j-1,k}\) 还没有被更新。

这似乎更那个区间 \(dp\) 直接枚举 \(i,j\) 一样是有问题的,于是我们可以像区间 \(dp\) 那样子先枚举长度就行了。

于是你得到:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stdlib.h>
#include <vector>
#include <iomanip>
#define int long long
#define N 305
using namespace std;
int n,a[N];
double f[N][N][N];
int sum1,sum2,sum3;
signed main(){
    cin >> n;
    for (int i = 1;i <= n;i ++) scanf("%lld",&a[i]);
    f[0][0][0] = 0.0;
    for (int i = 1;i <= n;i ++) sum1 += (a[i] == 1),sum2 += (a[i] == 2),sum3 += (a[i] == 3);
    for (int len = 1;len <= n;len ++)
        for (int i = 0;i <= n;i ++)
            for (int j = 0;i + j <= len;j ++) {
                int k = len - i - j;
                f[i][j][k] = 0;
                if (i) f[i][j][k] += f[i - 1][j][k] * i;
                if (j) f[i][j][k] += f[i + 1][j - 1][k] * j;
                if (k) f[i][j][k] += f[i][j + 1][k - 1] * k;
                f[i][j][k] = (f[i][j][k] + n) / (i + j + k);
            }
    cout << fixed << setprecision(10) << f[sum1][sum2][sum3];
    return 0;
}

这还是错的,同样的错误,应该从 \(k\)\(j\) 枚举。

代码

时间复杂度 \(\mathcal{O}(n^3)\),代码如下:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stdlib.h>
#include <vector>
#include <iomanip>
#define int long long
#define N 305
using namespace std;
int n,a[N];
double f[N][N][N];
int sum1,sum2,sum3;
signed main(){
    cin >> n;
    for (int i = 1;i <= n;i ++) scanf("%lld",&a[i]);
    f[0][0][0] = 0.0;
    for (int i = 1;i <= n;i ++) sum1 += (a[i] == 1),sum2 += (a[i] == 2),sum3 += (a[i] == 3);
    for (int len = 1;len <= n;len ++)
        for (int k = 0;k <= len;k ++)
            for (int j = 0;k + j <= len;j ++) {
                int i = len - k - j;
                f[i][j][k] = 0;
                if (i) f[i][j][k] += f[i - 1][j][k] * i;
                if (j) f[i][j][k] += f[i + 1][j - 1][k] * j;
                if (k) f[i][j][k] += f[i][j + 1][k - 1] * k;
                f[i][j][k] = (f[i][j][k] + n) / (i + j + k);
            }
    cout << fixed << setprecision(10) << f[sum1][sum2][sum3];
    return 0;
}

P6835 [Cnoi2020] 线形生物

题目概述

线形生物要从 \(1\) 号台阶走到 \(n+1\) 号台阶。

最开始,\(1,2,3,\ldots,n\) 号台阶都有一条连向下一台阶的有向边 \(i\rightarrow i+1\)

之后 Cirno 加入了 \(m\)返祖边 \(u_i \rightarrow v_i (u_i \ge v_i)\),它们构成了一个返祖图

线形生物每步会 等概率地 选取当前台阶的一条出边并走向对应的台阶。

当走到 \(n+1\) 号台阶时,线形生物就会停止行走。

同时,Cirno 会统计线性生物总共走的步数,记作 \(\delta\)

Cirno 想知道 \(E(\delta)\)(即 \(\delta\)数学期望)对 \(998244353\) 取模后的结果。

分析

这种随机游走的题目似乎很难去刻画他的状态,我们一步一步慢慢来(套路)。

首先我们要求:\(E_{1\rightarrow n}\)

根据期望的可加性,我们等价于求:

\[E_{1\rightarrow 2}+E_{2\rightarrow3}+\dots+E_{n\rightarrow n+1} \]

于是我们设 \(f_i\) 表示 \(i\rightarrow i+1\) 的期望步数,设 \(d_i\) 表示有多少条反祖边。

我们对于每个 \(x\) 有:

\[E_{x\rightarrow x+1}=\frac{1}{d_x+1}\times 1+\frac{\sum_v E_{v\rightarrow x +1}}{d_x+1} \]

整理一下:

\[E_{x\rightarrow x+1}=1+\frac{\sum_v E_{v\rightarrow x+1}}{d_x+1} \]

根据期望的可加性有:

\[E_{x\rightarrow x+1}=1+\frac{\sum_v\sum_{j=v}^i E_{j\rightarrow j+1}}{d_x+1} \]

\(E_{x\rightarrow x+1}\)\(\sum\) 中提出来移项并乘上 \(\frac{1}{d_x+1}\) 得到:

\[E_{x\rightarrow x+1}=d_x+1+\sum_v\sum_{j=v}^{i-1}E_{j\rightarrow j+1} \]

后面那个前缀和优化即可。

代码

时间复杂度 \(\mathcal{O}(n+m)\)

#include <iostream>
#include <cstdio>
#include <stdlib.h>
#include <cstring>
#include <algorithm>
#include <vector>
#define int long long
#define N 1000006
using namespace std;
const int mod = 998244353;
int qpow(int a,int b) {
    int res = 1;
    while(b) {
        if (b & 1) res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}
int f[N],sum[N];
int n,id,m;
vector<int> g[N];
signed main(){
    cin >> id >> n >> m;
    for (int i = 1;i <= m;i ++) {
        int x,y;
        scanf("%lld%lld",&x,&y);
        g[x].push_back(y);
    }
    for (int i = 1;i <= n;i ++) {
        f[i] = 1 + g[i].size();
        int res = 0;
        for (auto v : g[i]) res += sum[i - 1] - sum[v - 1],res = (res % mod + mod) % mod;
        f[i] += res;
        f[i] %= mod;
        sum[i] = sum[i - 1] + f[i];
        sum[i] %= mod;
        // cout << f[i] << ' ';
    }
    int ans = 0;
    for (int i = 1;i <= n;i ++) ans = (ans + f[i]) % mod;
    cout << ans;
    return 0;
}

P4550 收集邮票

题目概述

\(n\) 种邮票,第 \(i\) 次购买会花费 \(i\) 的代价等概率地得到一种邮票。

求集其所有种类邮票所需要代价的期望。

分析

这是带权的花费期望,我们考虑次数和权值的关系。

故设 \(f_{i}\) 表示还剩 \(i\) 种邮票没有买要达到目标所需要的期望次数。

显然:

\[f_i=(f_{i+1}+1)\times \frac{n-i}{n}+(f_i+1)\times\frac{i}{n} \]

得到:

\[f_i=f_{i+1}\frac{n-i}{n}+f_i\times\frac{i}{n}+1 \]

移项两边同乘 \(\frac{n}{n-i}\) 得到:

\[f_i=f_{i+1}+\frac{n}{n-i} \]

\(g_i\) 表示还剩 \(i\) 种邮票没有买要达到目标所需要的期望代价。
显然:

\[g_i = (g_{i+1}+f_{i+1}+1)\times{n-i}{n}+(g_i+f_i+1)\times\frac{i}{n} \]

整理得到:

\[g_i=(g_{i+1}+f_{i+1})\times{n-i}{n}+(g_i+f_i)\times\frac{i}{n}+1 \]

移项,两边同乘 \(\frac{n}{n-i}\) 得到:

\[g_i=g_{i+1}+f_{i+1}+f_i\times \frac{i}{n-i}+\frac{n}{n-i} \]

然后就做完了。

代码

时间复杂度 \(\mathcal{O}(n)\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <stdlib.h>
#include <vector>
#define int long long
#define N 10005
using namespace std;
int n;
double f[N],g[N];
signed main(){
    cin >> n;
    for (int i = n - 1;i >= 0;i --) {
        f[i] = 1.0 * n / (n - i) + f[i + 1];
        g[i] = g[i + 1] + f[i + 1] + 1.0 * n / (n - i) + 1.0 * i / (n - i) * f[i];
    } 
    printf("%.2f\n",g[0]);
    return 0;
}
posted @ 2025-10-05 16:43  high_skyy  阅读(6)  评论(0)    收藏  举报
浏览器标题切换
浏览器标题切换end