Alice and Bob are playing a simple game. They line up a row of n identical coins, all with the heads facing down onto the table and the tails upward.
For exactly m times they select any k of the coins and toss them into the air, replacing each of them either heads-up or heads-down with the same possibility. Their purpose is to gain as many coins heads-up as they can.
Input
The input has several test cases and the first line contains the integer t(1≤t≤1000) which is the total number of cases.
For each case, a line contains three space-separated integers n, m(1≤n,m≤100) and k (1≤k≤n).
Output
For each test case, output the expected number of coins heads-up which you could have at the end under the optimal strategy, as a real number with the precision of 33 digits.
样例输入
6 2 1 1 2 3 1 5 4 3 6 2 3 6 100 1 6 100 2
样例输出
0.500 1.250 3.479 3.000 5.500 5.000
最优的策略一定是:当有至少 k 枚硬币面朝下时,则选 k 枚面朝下的硬币去抛掷(任意k 枚都可以);如果不足 k 枚面朝下,则在选择所有面朝下的硬币的基础上再额外选择若干面朝上的银币。
于是有动态规划,记 F[i][j]表示抛掷了 i 次后,有 j 枚硬币面朝上的概率。他们应该满足F[i][0]+F[i][1]+...+F[i][n]=1。转移时,考虑从当前状态(i,j)出发,抛掷的 k 枚硬币的所有可能结果:分别有 0~k 枚面朝上。其中 k 枚硬币抛掷后有 l 枚面朝上的概率为 C(k,l)/2k。时间复杂度 O(nmk)。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
double d[200][200], p[200], c[200][200];
int main()
{
c[0][0] = 1;//计算前i个硬币中j个朝上的可能性有多少种;
for (int i = 1; i <= 100; i++)
{ //如前i个硬币j个朝上的可能性由前i-1个硬币j-1个朝上(则第i个硬币一定朝上)和前i-1个硬币j个朝上(则第i个一定朝下)共同组成;
c[i][0] = 1;
for (int j = 1; j <= i; j++)
c[i][j] = c[i - 1][j - 1] + c[i - 1][j];
}
p[0] = 1;//计算前i个硬币是反是正的可能性有多少种;2^k;
for (int i = 1; i <= 100; i++)
p[i] = p[i - 1] / 2;
int T;
cin >> T;
while (T--)
{
int n, m, t;
scanf_s("%d%d%d", &n, &m, &t);
memset(d, 0, sizeof d);
d[0][0] = 1;//动态规划,抛了i次后有j枚硬币朝上的可能性;
for (int i = 0; i<m; i++)
{
for (int j = 0; j <= n; j++)
{
if (d[i][j] == 0)continue;
for (int k = 0; k <= t; k++)//在原来的基础上,会不会新增k个朝上的可能;
{
if (n - j >= t)//当有至少 t 枚硬币面朝下时,则选 t 枚面朝下的硬币去抛掷(任意k 枚都可以)
d[i + 1][j + k] += d[i][j] * c[t][k] * p[t];
else //不足t枚反的硬币,则抛得时候选所有的反的和一些正的硬币去抛;
d[i + 1][j - (t - (n - j)) + k] += d[i][j] * c[t][k] * p[t];
}//此时已有j个朝上,但n-j表示反的,但n-j小于t,说明抛得时候必有正的,则n-j表示反的,(t-(n-j))表示抛得时候会额外选的正的,所以要减去;
}
}
double ans = 0;
for (int i = 1; i <= n; i++)
ans += d[m][i] * i;
printf("%.3lf\n", ans);
}
return 0;
}