概率期望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}\) 即可。
那么我们有转移:
化简有:
最后再除过去就可以了,于是你写出了以下的代码:
#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}\)。
根据期望的可加性,我们等价于求:
于是我们设 \(f_i\) 表示 \(i\rightarrow i+1\) 的期望步数,设 \(d_i\) 表示有多少条反祖边。
我们对于每个 \(x\) 有:
整理一下:
根据期望的可加性有:
将 \(E_{x\rightarrow x+1}\) 从 \(\sum\) 中提出来移项并乘上 \(\frac{1}{d_x+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\) 种邮票没有买要达到目标所需要的期望次数。
显然:
得到:
移项两边同乘 \(\frac{n}{n-i}\) 得到:
设 \(g_i\) 表示还剩 \(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;
}

浙公网安备 33010602011771号