CCF CSP 201803-4 棋局评估

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/IoT_fast/article/details/82556785

#问题描述
  Alice和Bob正在玩井字棋游戏。
  井字棋游戏的规则很简单:两人轮流往3*3的棋盘中放棋子,Alice放的是“X”,Bob放的是“O”,Alice执先。当同一种棋子占据一行、一列或一条对角线的三个格子时,游戏结束,该种棋子的持有者获胜。当棋盘被填满的时候,游戏结束,双方平手。
  Alice设计了一种对棋局评分的方法:
  - 对于Alice已经获胜的局面,评估得分为(棋盘上的空格子数+1);
  - 对于Bob已经获胜的局面,评估得分为 -(棋盘上的空格子数+1);
  - 对于平局的局面,评估得分为0;
  这里写图片描述
  例如上图中的局面,Alice已经获胜,同时棋盘上有2个空格,所以局面得分为2+1=3。
  由于Alice并不喜欢计算,所以他请教擅长编程的你,如果两人都以最优策略行棋,那么当前局面的最终得分会是多少?
#输入格式
  输入的第一行包含一个正整数T,表示数据的组数。
  每组数据输入有3行,每行有3个整数,用空格分隔,分别表示棋盘每个格子的状态。0表示格子为空,1表示格子中为“X”,2表示格子中为“O”。保证不会出现其他状态。
  保证输入的局面合法。(即保证输入的局面可以通过行棋到达,且保证没有双方同时获胜的情况)
  保证输入的局面轮到Alice行棋。
#输出格式
  对于每组数据,输出一行一个整数,表示当前局面的得分。
#样例输入
3
1 2 1
2 1 2
0 0 0
2 1 1
0 2 1
0 0 2
0 0 0
0 0 0
0 0 0
#样例输出
3
-4
0
#样例说明
  第一组数据:
  Alice将棋子放在左下角(或右下角)后,可以到达问题描述中的局面,得分为3。
  3为Alice行棋后能到达的局面中得分的最大值。
  第二组数据:
  这里写图片描述
  Bob已经获胜(如图),此局面得分为-(3+1)=-4。
  第三组数据:
  井字棋中若双方都采用最优策略,游戏平局,最终得分为0。
#数据规模和约定
  对于所有评测用例,1 ≤ T ≤ 5。

#解析
该题采用博弈论中的对抗搜索。首先需要理解下面几个知识点:
1.从MAX的观点看,可供自己选择的方案之间是“或”的关系,原因是主动权在自己手里,选择哪个方案完全由自己决定,可供自己选择的方案之间是“或”的关系,而对那些可供MIN选择的方案之间是“与”的关系,这是因为主动权在MIN手中,任何一个方案都可能被MIN选中,MAX必须防止那种对自己最不利的情况出现。
2.极大极小过程是考虑双方对弈若干步之后,从可能的走法中选一步相对好的走法来走,即在***有限的搜索深度范围内***进行求解。需要定义一个***静态估价函数f***,以便对棋局的态势做出评估。
3.MINMAX基本思想:
(1)当轮到MIN走步的节点时(取与时),MAX应考虑最坏的情况(即f§取极小值)。
(2)当轮到MAX走步的节点时(取或时),MAX应考虑最好的情况(即f§取极大值)。
(3)评价往回倒推时,相应于两位棋手的对抗策略,交替使用(1)和(2)两种方法传递倒推值。
所以这种方法称为极大极小过程。
4.若搜索空间较大,需要α-β剪枝。
5.本题中,通过搜索到棋局结束的得分来替换估价函数的估价值。
#源码

#include <iostream>
#include <fstream>
#include <cmath>

using namespace std;

int chess[4][4];   //存放棋局的全局矩阵 

//行满足 
bool RowWin(int role)
{
	for(int i=0;i<3;i++)
	{
		if(chess[i][0]==role&&chess[i][0]==chess[i][1]&&chess[i][1]==chess[i][2])
			return true;
	}
	return false;
}

//列满足 
bool ColWin(int role)
{
	for(int j=0;j<3;j++)
	{
		if(chess[0][j]==role&&chess[0][j]==chess[1][j]&&chess[1][j]==chess[2][j])
			return true;
	}
	return false;
}

//对角满足 
bool OppWin(int role)
{
	if(chess[0][0]==role&&chess[0][0]==chess[1][1]&&chess[1][1]==chess[2][2])
		return true;
	if(chess[0][2]==role&&chess[0][2]==chess[1][1]&&chess[1][1]==chess[2][0])
		return true;
	return false;
}

//判断当前棋局的空格子数 
int CountEmpty()
{
	int count=0;
	for(int i=0;i<3;i++)
	{
		for(int j=0;j<3;j++)
		{
			if(chess[i][j]==0) count++;
		}
	}
	return count;
}

//判断哪一方是否获胜的函数 
int JudgeWin(int role)   
{
	int score=0;
	if(RowWin(role)) score=CountEmpty()+1;
	if(ColWin(role)) score=CountEmpty()+1;
	if(OppWin(role)) score=CountEmpty()+1;
	if(score){
		return (role-1)?-score:score;   //0-Alice,1-Bob
	}
	else return score;
}

//根据行棋方进行搜索 
int dfs(int role)
{
	if(!CountEmpty()) return 0;
	int Max=-10,Min=10;
	for(int i=0;i<3;i++)
	{
		for(int j=0;j<3;j++)
		{
			if(chess[i][j]==0)
			{
				chess[i][j]=role+1;
				int w=JudgeWin(role+1);
				if(w)
				{
					chess[i][j]=0;
					return w>0?max(Max,w):min(Min,w);  //关键:当前若是Alice,则返回最大;若是Bob,则返回最小值 
				}
				if(!role) Max=max(Max,dfs(1));  //Alice选择最大值(极大) 
				else Min=min(Min,dfs(0));   //Bob选择最小值(极小)
				chess[i][j]=0;
			}
		}
	} 
	return role?Min:Max;
}

int main()
{
	//freopen("input/chess.txt","r",stdin);
	int T;   //数据的组数
	cin>>T;
	while(T--)
	{
		int val=0;  //存放当前棋局得分 
		for(int i=0;i<3;i++)
		{
			for(int j=0;j<3;j++)
			{
				cin>>chess[i][j];     //输入棋局 
			}
		}
		val=JudgeWin(1);   //判断初始棋局Alice是否获胜 
		if(val){
			cout<<val<<endl;
			continue;
		}
		val=JudgeWin(2);   //判断初始棋局Bob是否获胜
		if(val){
			cout<<val<<endl;
			continue;
		} 
		val=dfs(0);   //Alice行棋 
		cout<<val<<endl; 
	}
	
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/IoT_fast/article/details/82556785