题目

有两条蛇(1 号蛇和 2 号蛇)在 n 行 m 列的地图上,地图上有障碍物。一条蛇碰到蛇身/障碍物/边界就会死。蛇身会不断长长——可以理解为蛇尾位置不会变,蛇只会向前伸展不会缩尾巴。两条蛇都绝顶聪明,如果自己能赢,一定会尽量快地赢;如果自己会输,一定会死得尽量晚。给出初始局面,两蛇轮流走,每次可以且必须向上下左右移动一格。1 号蛇先走,请告诉我谁会在多少回合时赢。\((n,m\leq 20)\)\(0\)的数量不超过\(50\)


\(\alpha - \beta\)剪枝

AlphaBeta剪枝算法是一个搜索算法,旨在减少在其搜索树中,被极大极小算法评估的节点数。
这是一个常用人机游戏对抗的搜索算法。它的基本思想是根据上一层已经得到的当前最优结果,决定目前的搜索是否要继续下去——百度百科

前提:双方绝顶聪明,各自为营

推荐:博客1博客2

,这个剪枝方法是这样的一个结构:

  1. 所有的估价都是由同一个角度来看待的,不能说在1的回合说对1有利,在2的回合又说对2有利(应该说对1有害)

  2. 搜索树的叶子节点是估价函数,评估到达的这个状态的好坏,函数值越大对先手越有利,父亲节点的权值均由儿子转移来

  3. 奇偶性不同的层求的东西不一样,即两个人的策略不同,先手求的是所有儿子中最大的一个(即对先手最有利),后手求的是最小的一个(对先手最有害)

总而言之,就是搜索树的奇数层节点取\(max\),偶数层取\(min\),叶子节点需要自己估价(根据具体的题目)

扯了半天,如何剪枝?很简单,比如对于先手节点\(u\),它会取最大的儿子,假设当前权值为\(ans_u\),如果当前节点的儿子\(v\)的一个儿子\(vv\)权值小于\(ans\),就不用再遍历这个儿子\(v\)剩下的儿子了(因为儿子\(v\)的权值取min{孙子),这个儿子\(v\)的权值到目前为止可以判断一定小于\(ans_u\)了)


本题思路

本题显然可以用\(\alpha - \beta\)剪枝(双方绝顶聪明且操纵的蛇不一样)

主要是估价函数的设计:如果先手无路可走,就是绝对有害,设为\(-inf\),但又活的越久越好,所以还要加上一个存活时间\(step\),后手同理

这个剪枝还是比较简单好理解的,主要注意不要把先后大小关系搞反了就行

Code

#include<bits/stdc++.h>
#define N 25
#define Min(x,y) ((x)<(y)?(x):(y))
#define Max(x,y) ((x)>(y)?(x):(y))
using namespace std;
const int INF = 10000;
int n,m,a[N][N],sx[2],sy[2];
int dox[4]={0,1,0,-1},doy[4]={1,0,-1,0};

template <class T>
void read(T &x)
{
    char c;int sign=1;
    while((c=getchar())>'9'||c<'0') if(c=='-') sign=-1; x=c-48;
    while((c=getchar())>='0'&&c<='9') x=x*10+c-48; x*=sign;
}
int dfs(int step,int player,int alpha,int beta,int fx,int fy,int px,int py)//alpha是下界只降不增,beta是上界只增不降
{
    bool flag=false;
    int ans=(player==1 ? beta : alpha);//如果是1就会选尽量大
    for(int i=0;i<4;++i)
    {
        int dx=fx+dox[i],dy=fy+doy[i];
        if(dx<1||dy<1||dx>n||dy>m||a[dx][dy]) continue;
        flag=true;
        a[dx][dy]=1;
        int now=0;
        if(player==1) now=dfs(step+1,2,alpha,ans,px,py,dx,dy);//1号
        else now=dfs(step+1,1,ans,beta,px,py,dx,dy);
        a[dx][dy]=0;
        if(player==1)
        {
            if(now>=alpha) return alpha;
            //如果now比最小值大,那么这个节点会贡献值一定大于等于now,而最小值一定不会考虑它
            else ans=Max(ans,now);
        }
        else
        {
            if(now<=beta) return beta;
            else ans=Min(ans,now);
        }
    }
    if(!flag)//移动不了,即到了叶子(输),需要估价
    {
        if(player==1) return -INF+step;//step越大先手活的越久,但总体上还是自己输了
        else return INF-step;//step越大对于先手越坏,但总体上还是对面赢了
    }
    return ans;
}
int main()
{
    freopen("h.in","r",stdin);
    freopen("h.out","w",stdout);
    read(n);read(m);
    for(int i=1;i<=n;++i)
      for(int j=1;j<=m;++j)
      {
        read(a[i][j]);
        if(a[i][j]==1) sx[0]=i,sy[0]=j;
        if(a[i][j]==2) sx[1]=i,sy[1]=j;
      }

    int ans=dfs(1,1,INF,-INF,sx[0],sy[0],sx[1],sy[1]);
    if(ans>0) printf("1 %d\n",INF-ans);//最后到达的是2号对应的叶子,减掉INF还原
    else printf("2 %d\n",ans+INF);//最后到达的是1号对应的叶子,加上INF(这个见上面dfs中估价函数的计算)
    return 0;
}

P.S. 笔者小白一枚,如有错误还请指出

01-16 01:48