2018 安徽省赛试题 G 然后打五

2018 安徽省赛试题 G 然后打五

描述
《炉石传说》是一款考验技(shen)术(chou)的电子游戏,即使你没有玩过这个游戏也没有关系。《炉石传说》里有一张萨满卡牌叫做连环爆裂,它的效果是等概率的造成3-6点伤害。而游戏获胜的条件是将对方英雄的血量全部打掉。例如对方英雄还有5点生命值,你有1张连环爆裂,那么你获胜的概率为0.5。在本题中你无需考虑法力值消耗。
输入
第一行是一个整数T,代表测试数据的组数。接下来的T行每行有2个整数n,h,n代表你一共可以释放的连环爆裂的个数,h代表敌方英雄的血量。其中。
输出格式
共T行,每行是一个小数,保留到小数点后6位。
样例输入
1
1 5
样例输出
0.500000

首先看到这题想到的是数学中的重集的排列组合问题,对于每个元素都有选择和不选择的两种状态,而且还是需要考虑到各个元素相加起来大于血量的概率,时间复杂度会达到O(2^n)甚至更高。所以可以考虑优化这个问题的求解过程,利用递推关系,组合子问题的解求解这个问题。因为一张牌可以等概率的打掉对方3,4,5,6 滴血,所以设f(n,h)表示n张连环爆裂,敌方血量为h时的获胜概率,可以得到我方获胜的概率的递推的关系式:
f(n,h) = (f(n-1,h-3)+f(n-1,h-4)+f(n-1,h-5)+f(n-1,h-6))*1/4;

具体解法:
首先,可以根据上面的递推关系式,写成递归函数。因为当h < 3n时,表示就算每张卡牌都只会打掉对方3点血,我方获胜的概率为1;当h > 6n时,表示就算每张卡牌都能打掉对方6点血,我方获胜的概率也为0;还有当h和n都为0时,设获胜的概率为1;这些作为递归函数的出口。但是在上述递归的过程中,会出现子问题重叠的问题。例如求解f(2,5)时,会去求f(1,2),f(1,3),f(1,4),f(1,5)
,然后求f(1,3)的过程中会去求解f(0,0),求f(1,4)的过程中也会去求解f(0,0)等等。所以可以用动态规划的方法去解决这个问题。
因为f(n,h) = (f(n-1,h-3)+f(n-1,h-4)+f(n-1,h-5)+f(n-1,h-6))*1/4,用动态规划方法时,因为每次记录只会和上次相关,所以只需要记录本次和上一次的记录,即为记述次和偶数次的记录。所以可以开dp[2][30000]用于记录记述次和偶数次获胜的概率。

/*
#include <iostream>
#include <iomanip>
#include <vector>
using namespace std;

  
double f(int n, int h)
{
	if (h > 6 * n)
		return 0;
	else if (h == 0 && n == 0)
		return 1;
	else if (h < 3 * n)
		return 1;
	else
		return (f(n - 1, h - 3) + f(n - 1, h - 4) + f(n - 1, h - 5) + f(n - 1, h - 6))*0.25;
}
int main()
{
	int m;
	cin >> m;
	for (int i = 1; i <= m; i++)
	{
		int n, h;
		cin >> n >> h;
		cout << fixed << setprecision(6) << f(n, h) << endl;
	}
	system("pause");
	return 0;
}
*/

#include <iostream>
#include <iomanip>
#include <memory.h>
using namespace std;
double dp[2][30000];
int main() 
{
	int t;
	cin >>t;
	while (t--) 
	{
		int n, h;
		cin >> n >> h;
		memset(dp, 0, sizeof(dp));
		dp[0][h] = 1;
		for (int i = 1; i <= n; i++) 
		{
			for (int j = 0; j <= h; j++) 
			{
				for (int k = 3; k <= 6; k++) 
				{
					if (i % 2 == 1)
					{
						if (j > k)
							dp[1][j - k] = dp[1][j - k] + dp[0][j] / 4;
						else
							dp[1][0] = dp[1][0] + dp[0][j] / 4;
					}
					else
					{
						if (j > k)
							dp[0][j - k] = dp[0][j - k] + dp[1][j] / 4;
						else
							dp[0][0] = dp[0][0] + dp[1][j] / 4;
					}
				}
			}
		}
		if(n % 2 == 0)
			cout << fixed << setprecision(6) << dp[0][0];
		else
			cout << fixed << setprecision(6) << dp[1][0];
	}
	system("pause");
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值