ACM-ICPC 2017 Asia Urumqi:A. Coins(DP)

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;
}

猜你喜欢

转载自blog.csdn.net/yihanyifan/article/details/81910433