第十三次CCF CSP认证(2018年3月)真题棋局评估 题解

时间:2022-08-11 21:32:05

问题描述
  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行棋。
输出格式
  对于每组数据,输出一行一个整数,表示当前局面的得分。
数据规模和约定
  对于所有评测用例,1 ≤ T ≤ 5。

 

个人理解

              很直观,这个是个博弈论问题,A、B在既定棋盘状况下以最优策略使自己获胜。因为是3x3的棋盘,可以用一个9位数的数组来映射当前状态,进行记忆化搜索,因为对于某一个既定状态,它的答案是唯一的,所以搜过的状态无须再次搜索。搜索决策时注意起初当前状态无结果,搜索到一种就刷新,后续更新使最终答案正确的方法是 轮到A时在所有拓展方向中取结果最大的,B反之。两个搜索结束点是所有格子填满或一个选手赢得比赛。

   几个小点说一下,一个是判断某人是否获胜的方式,三子成一条线那么,第三个子的坐标一定是在第二个的基础上加上前两个的差值;另一个是终止状态中一定不要忘记都填满却平局的状态。

   在考场上因为观察到棋盘仅是3x3的,第一反应是打表,但在搜索决策时进入了一个很大误区,总想模拟作为一个棋手的所有决策方式,进而讲题目演化成一个巨大的模拟,耗时耗力最后发现误入歧途最终只得放弃。考后讨论后才明白记忆化搜索即为正确方法。

代码分享

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

long long sta;
int sve[222222223];
int che[4][4];
int t,mas,ans;

bool check(long long now)
{
   int x1[9],x2[9],y1[9],y2[9];
   int tot=0,a,b,x,y;
   a=b=0;
   for(int i=1;i<4;i++)
    for(int j=1;j<4;j++)
     {
         if(che[i][j]==0)tot++;
         else if(che[i][j]==1)
         {
             a++;
             x1[a]=i;y1[a]=j;
         }
         else
         {
             b++;
             x2[b]=i;y2[b]=j;
         }
     }
   if(a<3&&b<3)return false;
   if(a>=3)
   {
       for(int i=1;i<a;i++)
        for(int j=i+1;j<=a;j++)
       {
          x=x1[j]*2-x1[i];
          y=y1[j]*2-y1[i];
          if(x>0&&x<4&&y>0&&y<4)
          if(che[x][y]==1)
          {
              sve[now]=tot+1;
              return true;
          }
       }
   }
   if(b>=3)
   {
       for(int i=1;i<b;i++)
        for(int j=i+1;j<=b;j++)
       {
          x=x2[j]*2-x2[i];
          y=y2[j]*2-y2[i];
          if(x>0&&x<4&&y>0&&y<4)
          if(che[x][y]==2)
          {
              sve[now]=-(tot+1);
              return true;
          }
       }
   }
   if(tot==0){
    sve[now]=0;
    return true;
   }
   return false;
}
long long cla()
{
    long long tot=0;;
    for(int i=1;i<4;i++)
            for(int j=1;j<4;j++)
        {
            tot=che[i][j]+tot*10;
        }
    return tot;
}
void dfs(long long now,int who)
{
    long long aft;
    if(sve[now]!=mas)return;
    if(check(now))return;
    for(int i=1;i<4;i++)
      for(int j=1;j<4;j++)
      {
          if(che[i][j]==0)
          {
              che[i][j]=who;
              aft=cla();
              dfs(aft,3-who);
              if(sve[now]==mas)sve[now]=sve[aft];
              if(who==1&&sve[aft]>sve[now])sve[now]=sve[aft];
              if(who==2&&sve[aft]<sve[now])sve[now]=sve[aft];
              che[i][j]=0;
          }
      }
}
int main()
{
    scanf("%d",&t);
    memset(sve,127,sizeof(sve));
    mas=sve[1];
    while(t--)
    {
        sta=0;
        for(int i=1;i<4;i++)
            for(int j=1;j<4;j++)
        {
            scanf("%d",&che[i][j]);
            sta=che[i][j]+sta*10;
        }
        dfs(sta,1);
        ans=sve[sta];
        printf("%d\n",ans);
    }
    return 0;
}
 

--------------------------------------------更新分割线----------------------------------------------------

  首先要感谢@qq_37482918的提醒,认证后写完代码和帖子就去做别的事情了,后来ccf官网出了题库就兴冲冲交了一发,结果意外的WA了,当然是爆掉了!

第十三次CCF CSP认证(2018年3月)真题棋局评估 题解    因为在编译器里操作肯定不会出这个问题,后来就想到了因为每个点状态无非是三种,所以很自然想到用三进制优化一下,将 数据量就极大地降下来了,因为在原来的sve数组利用率只有3^9/222222223=0.0088%.

    优化后提交就过了。

    第十三次CCF CSP认证(2018年3月)真题棋局评估 题解

   简单说下优化方式,原来状态的表示方法不便,只是每次存状态、取状态时将原来的状态数转化为三进制所代表的十进制,即turn方法。进制转化详见点击打开链接

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

long long sta;
int sva[20000];
int che[4][4];
int t,mas,ans;

int turn(long long x)
{
    int re=0;
    int t[9];
    for(int i=0;i<9;i++)
    {
        t[i]=x%10;
        x=x/10;
    }
    for(int i=8;i>=0;i--)
    {
        re=re*3+t[i];
    }
    return re;
}
bool check(long long now)
{
   int x1[9],x2[9],y1[9],y2[9];
   int tot=0,a,b,x,y;
   a=b=0;
   for(int i=1;i<4;i++)
    for(int j=1;j<4;j++)
     {
         if(che[i][j]==0)tot++;
         else if(che[i][j]==1)
         {
             a++;
             x1[a]=i;y1[a]=j;
         }
         else
         {
             b++;
             x2[b]=i;y2[b]=j;
         }
     }
   if(a<3&&b<3)return false;
   if(a>=3)
   {
       for(int i=1;i<a;i++)
        for(int j=i+1;j<=a;j++)
       {
          x=x1[j]*2-x1[i];
          y=y1[j]*2-y1[i];
          if(x>0&&x<4&&y>0&&y<4)
          if(che[x][y]==1)
          {
              sva[turn(now)]=tot+1;
              return true;
          }
       }
   }
   if(b>=3)
   {
       for(int i=1;i<b;i++)
        for(int j=i+1;j<=b;j++)
       {
          x=x2[j]*2-x2[i];
          y=y2[j]*2-y2[i];
          if(x>0&&x<4&&y>0&&y<4)
          if(che[x][y]==2)
          {
              sva[turn(now)]=-(tot+1);
              return true;
          }
       }
   }
   if(tot==0){
    sva[turn(now)]=0;
    return true;
   }
   return false;
}
long long cla()
{
    long long tot=0;;
    for(int i=1;i<4;i++)
            for(int j=1;j<4;j++)
        {
            tot=che[i][j]+tot*10;
        }
    return tot;
}
void dfs(long long now,int who)
{
    long long aft;
    int x,y;
    x=turn(now);
    if(sva[x]!=mas)return;
    if(check(now))return;
    for(int i=1;i<4;i++)
      for(int j=1;j<4;j++)
      {
          if(che[i][j]==0)
          {
              che[i][j]=who;
              aft=cla();
              y=turn(aft);
              dfs(aft,3-who);
              if(sva[x]==mas)sva[x]=sva[y];
              if(who==1&&sva[y]>sva[x])sva[x]=sva[y];
              if(who==2&&sva[y]<sva[x])sva[x]=sva[y];
              che[i][j]=0;
          }
      }
}
int main()
{
    scanf("%d",&t);
    memset(sva,127,sizeof(sva));
    mas=sva[1];
    while(t--)
    {
        sta=0;
        for(int i=1;i<4;i++)
            for(int j=1;j<4;j++)
        {
            scanf("%d",&che[i][j]);
            sta=che[i][j]+sta*10;
        }
        dfs(sta,1);
        ans=sva[turn(sta)];
        printf("%d\n",ans);
    }
    return 0;
}