牛客题单__动态规划课程概率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位的概率。
思路:
#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;
}