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

来源:互联网 发布:淘宝商家联盟 编辑:程序博客网 时间:2024/06/06 12:22

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





0 0
原创粉丝点击