• [CSP校内集训]贪吃蛇(阿尔法-贝塔剪枝)


    题目

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


    (alpha - eta)剪枝

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

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

    用我的话来说,这个剪枝方法是这样的一个结构:

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

    2. 搜索树上先手通过一条边转移成后手

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

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

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

    扯了半天,如何剪枝?很简单,比如对于先手节点(u),它会取最大的儿子,假设当前权值为(ans_u),如果当前节点的儿子(v)的一个儿子(vv)权值小于(ans_u),就不用再遍历这个儿子(v)剩下的儿子了(因为儿子(v)的权值取(min){孙子}),这个儿子(v)的权值到目前为止可以判断一定小于(ans_u)了;后手相反(可能会有点抽象,建议画图理解)


    本题思路

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

    主要是估价函数的设计:如果先手无路可走,就是绝对有害,设为(-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)//注意这里的玩家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
    ",INF-ans);//最后到达的是2号对应的叶子,减掉INF还原 
    	else printf("2 %d
    ",ans+INF);//最后到达的是1号对应的叶子,加上INF(这个见上面dfs中估价函数的计算) 
    	return 0;
    }
    

    (learning from)博客1博客2

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

  • 相关阅读:
    C# Process执行bat
    Auto Clear Unity Console Log
    Unity原厂讲师大解密
    setSystemUiVisibility() 与 getSystemUiVisibility() 显示隐藏状态栏
    Android 6.0 动态权限申请
    Android6.0动态权限申请步骤以及需要注意的一些坑
    基于Rebound制造绚丽的动画效果-入门篇
    Rebound-Android的弹簧动画库
    Rebound动画框架简单介绍
    让动画不再僵硬:Facebook Rebound Android动画库介绍
  • 原文地址:https://www.cnblogs.com/Chtholly/p/11650808.html
Copyright © 2020-2023  润新知