扑克牌游戏 概率 贪心

题目描述

有2N张扑克牌,每张扑克牌都印有一个整数,整数的范围是[1,2N],而且所有扑克牌的整数都不相同。

Alice和Bob在玩这个游戏。Alice选出其中的N张扑克牌,Bob拿剩下的N张扑克牌。接着游戏进行N轮。每一轮游戏,Alice出一张牌,Bob也出一张牌,如果Alice出的牌印着的整数比Bob的大,那么Alice可以得1分,否则Alice得0分。

给出数组b[1…N],对于1<=i<=N,如果b[i]等于-1, 表示Bob第i轮未确定出的是哪张扑克牌,如果b[i]不等于-1, 则表示Bob第i轮出的扑克牌印着的整数是b[i]。

给出数组a[1…N],表示的是Alice从2*N张牌当中选出了这N张牌,但Alice的出牌次序还没决定。
现在Alice要在游戏的第一轮开始之前就确定好他自己的出牌次序,一旦确定,后面就不能改变出牌次序了。

Alice应该如何确定次序,使得Alice游戏结束后得分的期望值最大?输出该期望值。

输入格式

多组测试数据。

第一行,一个整数G,表示有G组测试数据。1 <= G <= 10。

每组册数数据格式:

第一行,一个整数N。1 <= N <= 50。

第二行,N个整数,第i个数表示a[i]。1 <= a[i] <= 2*N。

第三行,N个整数,第i个数表示b[i]。

输出格式

共G行,每行一个实数。误差不能超过0.000001。

输入样例

5
2
4 2 
-1 -1 
2
4 2 
1 3 
1
2 
-1 
4
1 3 5 7 
8 -1 4 -1 
10
6 12 17 14 20 8 16 7 2 15 
-1 -1 4 -1 11 3 13 -1 -1 18 

输出样例

1.5
2.0
1.0
2.5
8.0

解题思路:

本题大意:帮助A确定出牌顺序,让A在此出牌顺序下得到最高分。

  1. 对于已知B出什么牌的回合,一定要尽可能多的高分
  2. 对于未知B出什么牌的回合,计算它的期望值

对于第一个问题,相信聪明的OIer都会想到贪心,这一种贪心很明显就是田忌赛马的策略:对于可以得分的B牌,一定要用自己手上能获胜且最小的牌得分;对于不可能得分的B牌,一定要用自己手上最小的牌当“炮灰”。

为什么这个策略可以让最开始的期望值最大呢?我们来思考一下:


A手上的牌有 1 4 5

B的出牌顺序 2 -1 -1

根据题意,可以确定的是B剩下的两张牌是3和6,对于现在B要出的第一张牌,有两种情况:

  1. 用1输给2
    结果:没有分
  2. 用4赢了2
    结果:获得1分
  3. 用5赢了2
    结果:获得1分

如果我们使用了1策略,那么在比赛结束后我们最多获得1分,最少获得0分。

但是如果我们使用2策略,那么在比赛结束后我们最多获得2分,最少获得1分。

由此可见,2策略是最好的,若在对阵所有未知B牌时,我们全部输掉了,那么只有在对阵所有已知牌获得最高分才是我们的最佳策略。

我们再来对比一下2策略和3策略,很明显可以得出结论:当对阵未知B牌时,手上剩下的牌越大,得分概率越大。

综上所述,田忌赛马的贪心策略在本题是可行的!


使用上面的策略,将最大得分计入答案。

第一个问题迎刃而解。

在操作完第一个问题后,我们要用剩下的牌解决第二个问题:求期望值。

我们考虑全部未知B牌和全部手中剩下的牌,对于每一个手中剩下的牌,都有可能与每一个未知B牌一一对应,我们循环遍历未知B牌,如果当前这一个手中的牌可以获胜得分,那么答案加上这个情况的概率。

第二个问题也解决了。

代码

#include<iostream>
#include<algorithm>
#include<iomanip>
#include<fstream>

using namespace std;
int G,N,a[105],b[105];
bool bo[105],chosen[105],beat;
int unknow[105],len,last[105],len2;
double ans;

//bo[]:用数组计数统计哪些数字出现了,哪些数字未确定

int main()
{
    
    
	freopen("2811.in","r",stdin);
	freopen("2811.out","w",stdout);
	cin>>G;
	for(int g=1;g<=G;g++)
	{
    
    
		ans=0;
		len=0;
		len2=0;
		cin>>N;
		for(int i=1;i<=2*N;i++)
			bo[i]=chosen[i]=last[i]=unknow[i]=0;//初始化
		for(int i=1;i<=N;i++)
		{
    
    
			cin>>a[i];
			bo[a[i]]=1;
		}
		for(int i=1;i<=N;i++)
		{
    
    
			cin>>b[i];
			bo[b[i]]=1;
		}
		sort(a+1,a+1+N);//从小到大排序
		sort(b+1,b+1+N);
		for(int i=1;i<=2*N;i++)
			if(!bo[i])
				unknow[++len]=i;//unknow[]:未知出牌顺序的B牌
		for(int i=N;b[i]!=-1;i--)//从大到小遍历
		{
    
    
			beat=0;
			for(int j=N;j>=1;j--)//从大到小遍历
				if(a[j]>b[i]&&chosen[j]==0)//如果当前手中的牌比已知B牌大
				{
    
    
					int k=j-1;
					while(chosen[k]==1) k--;//查看存不存在a[k]<a[j]且a[k]>b[i]的牌
					if(a[k]<b[i])//a[k]不存在
					{
    
    
						beat=1;
						chosen[j]=1;
						ans++;
						break;
					}
				}
			if(!beat)//a[k]存在
			{
    
    
				for(int j=1;j<=N;j++)
					if(!chosen[j])
					{
    
    
						chosen[j]=1;
						break;
					}
			}
		}
		for(int i=1;i<=N;i++)//筛出手中剩下的牌
			if(!chosen[i])
			{
    
    
				len2++;
				last[len2]=a[i];
			}
		for(int i=1;i<=len;i++)//遍历last和unknow
			for(int j=1;j<=len2;j++)
				if(last[i]>unknow[j])
					ans+=1.0000000/double(len);//求期望值
		cout<<fixed<<setprecision(7)<<ans<<endl;
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/bell041030/article/details/88765710