题解:UVA10111 Find the Winning Move
思路:
爆搜肯定不行,考虑 Minimax 加 Alpha-Beta 剪枝。不会的可以看这里。
我们通过 Minimax 算法模拟两名玩家交替下棋的过程,枚举可供落子的点,落子后继续搜索之后的状态,直到出现以下情况:
-
某一方获胜,此时返回一个状态,例如我方获胜返回
1 ,敌方获胜返回-1 。我方获胜的返回值需大于敌方获胜的返回值,因为后面的 Alpha-Beta 剪枝要用。 -
棋盘填满,此时返回
0 。返回的值介于两者中间。
再设一个可能达到的最大下界,用
最开始时我们枚举一个我方落子的点,若能获胜直接返回坐标,否则跑 Minimax 搜索去评估在这个点落子的优劣性,若返回值等于
否则输出无解。
实现细节:
棋盘落子需改变棋盘的状态,为了方便,我们用临时数组存储落子后棋盘的状态,搜索时当做参数传进去即可。
代码,可以看看注释:
#include<bits/stdc++.h>
using namespace std;
#define gc getchar
template<typename T>inline void read(T&x){x=0;char ch=gc();while(ch<'0'||ch>'9')ch=gc();while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=gc();}}
template<typename T,typename ...T1>inline void read(T&x,T1&...x1){read(x);read(x1...);}
const int ri=105;
struct node{int x,y; node(int l=-1,int r=-1):x(l),y(r){}};//结构体存坐标
char T[ri][ri]; string s,a[ri]; int tot;
bool check(char E[ri][ri],char player){//判断player这方是否获胜
for(int i=0;i<4;i++){
if(E[i][0]==player&&E[i][1]==E[i][2]&&E[i][2]==E[i][3]&&E[i][0]==E[i][1])
return true;
}
for(int i=0;i<4;i++){
if(E[0][i]==player&&E[1][i]==E[2][i]&&E[2][i]==E[3][i]&&E[0][i]==E[1][i])
return true;
}
if(E[1][1]==player&&E[2][2]==player&&E[3][3]==player&&E[0][0]==player)
return true;
if(E[0][3]==player&&E[1][2]==player&&E[2][1]==player&&E[3][0]==player)
return true;
return false;
}bool full(char E[ri][ri]){//判断棋盘是否被填满,即平局
for(int i=0;i<4;i++) for(int j=0;j<4;j++) if(E[i][j]=='.') return false;
return true;
}int alpha_beta(char Y[ri][ri],int alpha,int beta,bool ismax){//Minimax搜索+Alpha-Beta剪枝
if(check(Y,'x')) return 1;//我方获胜
if(check(Y,'o')) return -1;//敌方获胜
if(full(Y)) return 0;//平局
if(ismax){//取最优值,即我方落子
int mx=-0x3f3f3f3f;
for(int i=0;i<4;i++){//枚举可以落子的点
for(int j=0;j<4;j++){
if(Y[i][j]=='.'){
char T[ri][ri];
for(int x=0;x<4;x++) for(int y=0;y<4;y++) T[x][y]=Y[x][y];
T[i][j]='x'; int tmp=alpha_beta(T,alpha,beta,false);
mx=max(mx,tmp); alpha=max(alpha,tmp);//更新,取最优值
if(beta<=alpha) break;//Alpha-Beta剪枝
}
}
if(beta<=alpha) break;
}
return mx;
}else{////取最劣值,即敌方落子
int mi=0x3f3f3f3f;
for(int i=0;i<4;i++){//同理,枚举可以落子的点
for(int j=0;j<4;j++){
if(Y[i][j]=='.'){
char T[ri][ri];
for(int x=0;x<4;x++) for(int y=0;y<4;y++) T[x][y]=Y[x][y];
T[i][j]='o'; int tmp=alpha_beta(T,alpha,beta,true);
mi=min(mi,tmp); beta=min(beta,tmp);//更新,取最劣值
if(beta<=alpha) break;
}
}
if(beta<=alpha) break;
}
return mi;
}
}node move(char E[ri][ri]){
for(int i=0;i<4;i++){
for(int j=0;j<4;j++){
if(E[i][j]=='.'){//枚举可以落子的点
char T[ri][ri];
for(int x=0;x<4;x++) for(int y=0;y<4;y++) T[x][y]=E[x][y];
T[i][j]='x'; if(check(T,'x')) return node(i,j);//直接获胜
if(alpha_beta(T,-0x3f3f3f3f,0x3f3f3f3f,false)==1) return node(i,j);//有必胜策略
}
}
}
return node();//没有必胜策略
}
int main(){
while(getline(cin,s)&&s!="$") a[++tot]=s;
for(int i=1;i<=tot; ){
if(a[i]=="?"){
i++; char T[ri][ri];//临时存棋盘
for(int x=0;x<4;x++,i++) for(int y=0;y<4;y++) T[x][y]=a[i][y];
node mov=move(T);
if(mov.y==-1) puts("#####");
else printf("(%d,%d)\n",mov.x,mov.y);
}else i++;
}
return 0;
}