#问题描述
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;
}