UVALive - 8468 (计蒜客) Coins 概率dp+详解

题意:

给定n个硬币,初始美国都是方面超上,m次机会,每次必须选k翻转,问采取最优策略的翻转方式,得到的正面朝上的期望是多少

思路:

这题按说需要高精度,但是懒得写,用double过了,可能是因为保留的结果的位数较小,不会误差那么大

首先,最优策略就是:每次选取k个的时候,我们先选反面朝上的硬币,如果不够,再选正面朝上的,

因为每次的翻转只跟上次的翻转情况有关,所以我们可以推导一下递推式dp,

其实很容易想到dp[i][j] 表示 翻转完第i次时,j个硬币正面朝上的概率;

如果不知道,可以这样考虑:首先dp得有一维表示翻转次数,用来递推相邻两次翻转关系;其次每次翻转影响的是正面朝上的硬币的个数(因为我们要求的就是这个,所以不用反面朝上的个数),所以有一维需要表示正面朝上的个数;最后还要表示当前状态的概率,那就是直接用dp[][] 表示了,即可以得到dp[i][j];

递推式:我们得到了dp[i][j] 第 i 次翻转后有 j 个正面朝上,这时候我们进行第 i+1 次翻转,会遇到两种情况:

1. j+k<=n: 即我们可以选k个反面朝上的硬币操作,然后可以求得这k个硬币中朝上的个数 t 的概率p,那么dp[i+1][j+t] = dp[i][j]*p;    p = C(k,t)* (1/2)^k

2. j+k > n:  即需要选择tt {=(j+k-n)} 个正面朝上的硬币进行翻转,所以第i+1次翻转后确定的正面朝上的个数为j-tt个 ,然后考虑k个刚翻转的正面朝上的个数t 的概率p,p还是C(k,t)* (1/2)^k;所以此时递推式为 dp[i+1][j-tt+t] = dp[i][j]*p;

扫描二维码关注公众号,回复: 2757713 查看本文章

可以发现:对于给定的k,t 的概率p恒为 C(k,t)* (1/2)^k,所以我们可以先预处理这一部分;

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <map>
#include <queue>
#include <vector>

using namespace std;
typedef long long ll;
const int maxn = 100 + 7;

int n, m, k;
double dp[maxn][maxn];
double c[maxn][maxn];

void init() {
  double t1 = 1;
  for(int i = 1; i <= k; ++i) t1 *= 2.0;
  c[k][0] = 1;
  for(int i = 1; i <= k; ++i) {
    c[k][i] = c[k][i-1]*(k-i+1)/i;
  }
  for(int i = 0; i <= k; ++i) {
    c[k][i] /= t1;
  }
}
int main() {
  int T;
  scanf("%d", &T);
  while(T--) {
    scanf("%d%d%d", &n, &m, &k);
    init();
    memset(dp, 0, sizeof dp);
    dp[0][0] = 1.0;
    for(int i = 0; i < m; ++i) {
      for(int j = 0; j <= n; ++j) {
        if(j + k <= n) {
          for(int t = 0; t <= k; ++t) {
            dp[i+1][j+t] += dp[i][j]*c[k][t];
          }
        } else {
          int tt = j+k-n;
          for(int t = 0; t <= k; ++t) {
            dp[i+1][j+t-tt] += dp[i][j]*c[k][t];
          }
        }

      }
    }
    double ans = 0;
    for(int i = 0; i <= n; ++i) {
      ans += (i*dp[m][i]);
    }
    printf("%.3f\n", ans);
  }

  return 0;
}

猜你喜欢

转载自blog.csdn.net/xiang_6/article/details/81624569