poj 3317 Stake Your Claim(极大极小搜索+记忆化搜索+状态压缩)

题意:给定一个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;
}





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值