题意:给定一个n*n的矩阵(n<=8),两个人依次向矩阵里放0和1,玩家的得分为自己牌的数量减去对方牌的数量。
问先手的下一步最佳得分位置和得分。
设先手放0. c0为0的数量,c1为1的数量。
那么先手得分为c0 - c1;
后手得分为c1-c0;
假设估价函数为g(x)=c0(x)-c1(x).x为某一状态。
可以看出,先手求当前所有选择中g(x)的最大值,后手求当前所有选择中的最小值。
这就是博弈中的极大极小搜索了。
直接爆搜的话会有许多的重复状态。(每次只是换了个地方放0或1,很容易重复)
由于空位小于等于10。考虑用三进制压缩状态。
0表示当前位空,1表示放0, 2表示放1.
剪枝的话,可以用alpha-beta剪枝,不过此题没什么效果。
#include<cstdio>
#include<cstring>
#define MAX(a,b) a>b?a:b
using namespace std;
const int INF=(1<<30);
char map[12][12];
int empty[12];int full[12];//空位
int cnt;
int n;
bool vis[12][12]={false};
int dx[4]={0,0,1,-1},dy[4]={1,-1,0,0};
int c3[12];//c3[i]记录 3^i
//估价函数为g(x)=c0(x)-c1(x);状态x的情况下 0的个数-1的个数
struct Point{
int x,y;
int score;
Point(){}
Point(int a,int b,int c){x=a;y=b;score=c;}
}dp[60000];
bool cmp(struct Point Z,int i)
{
int xx=empty[i]/n,yy=empty[i]%n;
if(xx!=Z.x)return xx<Z.x;
return yy<Z.y;
}
void init()
{
cnt=0;
}
bool check(int x,int y){
if(x<0||x>=n||y<0||y>=n)return 0;
return 1;
}
int xx=0;
void dfs(int x,int y,int c)
{
vis[x][y]=1;
xx++;
for(int i=0;i<4;i++){
int x1=x+dx[i],y1=y+dy[i];
if(check(x1,y1)&&!vis[x1][y1]&&map[x1][y1]==c+'0'){
dfs(x1,y1,c);
}
}
}
int getsum()
{
memset(vis,0,sizeof(vis));
int c0=0,c1=0;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
if(!vis[i][j]){
xx=0;
if(map[i][j]=='0')dfs(i,j,0),c0=MAX(c0,xx);
else if(map[i][j]=='1') dfs(i,j,1),c1=MAX(c1,xx);
}
}
}
return c0-c1;//估价函数
}
struct Point MaxSearch(int state,int now,int pos);
struct Point MinSearch(int state,int now,int pos);
struct Point MaxSearch(int state,int now,int pos){//state为当前棋盘是否填充的二进制状态。now为填充棋盘的三进制状态
struct Point Z,Y;
if(state==0){
Z.score=getsum();
Z.x=empty[pos]%n;Z.y=empty[pos]/n;
return Z;
}
if(dp[now].score!=-INF)return dp[now];
int st=state;
Z.score=-INF;//ans为本层最优值。
while(st)
{
int k=st&(-st),cur;//k为最右边的1,即从右往左依次选择空棋盘位置。
for(cur=0;cur<cnt;cur++){
if((1<<cur)&k)break;
}
map[empty[cur]/n][empty[cur]%n]='0';
Y=MinSearch(state^k,now+c3[cur],cur);
map[empty[cur]/n][empty[cur]%n]='.';
if(Z.score<Y.score||(Z.score==Y.score&&cmp(Z,cur)))Z.score=Y.score,Z.x=empty[cur]/n,Z.y=empty[cur]%n;
st-=k;
}
return dp[now]=Z;
}
struct Point MinSearch(int state,int now,int pos)
{
struct Point Z,Y;
if(state==0){
Z.score=getsum();
Z.x=empty[pos]%n;Z.y=empty[pos]/n;
return Z;
}
if(dp[now].score!=-INF)return dp[now];
int st=state;
Z.score=INF;//ans为本层最优值。
while(st)
{
int k=st&(-st),cur;//k为最右边的1,即从右往左依次选择空棋盘位置。
for(cur=0;cur<cnt;cur++){
if((1<<cur)&k)break;//cur为从右数的空位标号。
}
map[empty[cur]/n][empty[cur]%n]='1';
Y=MaxSearch(state-k,now+2*c3[cur],cur);
map[empty[cur]/n][empty[cur]%n]='.';
if(Z.score>Y.score||(Z.score==Y.score&&cmp(Z,cur)))Z.score=Y.score,Z.x=empty[cur]/n,Z.y=empty[cur]%n;
st-=k;
}
return dp[now]=Z;
}
void input()
{
int cnt0,cnt1;
cnt0=cnt1=0;
for(int i=0;i<n;i++){
scanf("%s",map[i]);
for(int j=0;j<n;j++)
{
if(map[i][j]=='1')cnt1++;
if(map[i][j]=='0')cnt0++;
if(map[i][j]=='.')empty[cnt++]=i*n+j;
}
}
if(cnt0>cnt1){//都改成0为先手
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
if(map[i][j]=='0')map[i][j]='1';
else if(map[i][j]=='1')map[i][j]='0';
}
}
}
for(int i=0;i<c3[cnt];i++)dp[i].score=-INF;
}
int main()
{
c3[0]=1;
for(int i=1;i<=10;i++)
c3[i]=c3[i-1]*3;
while(scanf("%d",&n)!=EOF&&n){
init();
input();
Point ans=MaxSearch((1<<cnt)-1,0,0);
printf("(%d,%d) %d\n",ans.x,ans.y,ans.score);
}
return 0;
}