对抗搜索 学习笔记
创始人
2024-05-07 14:57:34
0次

先来看一道有意思题

situation

大意:

两个人玩井字棋,要求把所有位置填满后结束游戏。一方希望两者的连起来的线之差最多,一方希望最少。现在给定初始局面(已经存在一些棋子)以及先手,求最后的两者的连起来的线之差。

这就是一道对抗搜索的题目

什么是对抗搜索?简单来说,博弈双方对竞争相反的利益,一方使其最大化,一方使其最小化,那么我们就可以通过搜索来探索最终状态

min-max对抗搜索

假设我们已经有了一颗博弈树:
 

 如果我方先手且我方以最大化利益为目标,那么单数层就是我方的决策层,双数层就是对方的。那么在单数层的节点,它会选取利益最大化的子局面,反之亦然。

现在我们从最底层开始向上,D在单数层,它会选取最大的利益,所以它的利益为2。B就会在D和E的利益之间选取最小的...依次类推,最后我们就可以得到初始局面对应的利益了。

但是当博弈规模比较大的时候,搜索规模也会爆炸,就要考虑剪枝。

这里引入:

Alpha_Beta对抗搜索

我们限定一个利益区间【a,b】

α Alpha is the maximum lower bound of possible solutions 

对于一个追求max利益的节点P,它的所有子节点都是追求min利益,会将收益尽可能降低,那么P就会在所有尽可能低的收益里选最高的,也就是α了。

β Beta is the minimum upper bound of possible solutions

β同理。是在所有尽可能高的收益里取低

换句话说,α和β其实都是对应最差情况下的收益。

那么α会如何更新?显然被每一个子局面的最差情况更新,也就是子局面的β,同理β就会被子局面的α更新。

初始时a=-inf,b=inf(显然)

不难发现,对于一个追求最大收益的节点,如果它的子节点(都追求最小化收益)存在一个子局面,能够获得比a更小的收益,我们就可以剪掉对应子树,不然利益一定不会在区间范围内

同理,对于一个追求最小化收益的节点,如果子节点存在一个可以获得比b更大的收益,就要剪掉

否则我们的子节点就可以更新当前区间。区间不断缩小,最后确定收益值。


另外这里再考虑一下遍历的问题。显然一个父节点的区间端点要再子节点的端点里取极值,那么层序遍历(从下往上)就是不合理的,因为这样就还要去记录子节点的值。更好的是采用先序/后序遍历,就可以在搜索的时候完成更新了。

题解

最后讲回这题。

因为数据规模非常小,直接做min-max对抗搜索就可以了。对于每一个确定的局面我们可以很轻松就确定其对应的收益,那么剩余情况就暴力dfs就好了。然后为了区分情况方便记忆化,可以哈希一下。

code

#include
using namespace std;
#define ll long long
#define endl '\n'
const int inf=0x3f3f3f3f;
const ll N=3e5+10;
string s;struct ty//局面 
{char tr[5][5];bool judge(ll id,ll op){if(op==0)//横排{for(int i=2;i<=3;++i){if(tr[id][i]!=tr[id][i-1]) return 0;	}	return 1;} else if(op==1)//竖排 {for(int j=2;j<=3;++j){if(tr[j][id]!=tr[j-1][id]) return 0;}return 1;}else if(op==2)//斜着 {if(id==1){for(int i=2;i<=3;++i){if(tr[i][i]!=tr[i-1][i-1]) return 0;}return 1;} else if(id==2){for(int i=2;i<=3;++i){if(tr[i][4-i]!=tr[i-1][5-i]) return 0;}return 1;}}}ll get_haxi(){ll haxi=0;//哈希值 for(int i=1;i<=3;++i)//三进制表示 {for(int j=1;j<=3;++j){haxi*=3;if(tr[i][j]=='O') haxi++;else if(tr[i][j]=='X') haxi+=2;}}return haxi;}int get(){int ans=0;for(int i=1;i<=3;++i){if(judge(i,0)==0) continue;else if(tr[i][1]=='O') ans++;else ans--;}for(int i=1;i<=3;++i){if(judge(i,1)==0) continue;else if(tr[1][i]=='O') ans++;else ans--;}if(judge(1,2)){if(tr[1][1]=='O') ans++;else ans--;}if(judge(2,2)){if(tr[2][2]=='O') ans++;else ans--;}return ans;}bool jd(){for(int i=1;i<=3;++i){for(int j=1;j<=3;++j){if(tr[i][j]=='.') return 1;}}return 0;}
}; 
int dp[N][3];
int dfs(ty now,int op)
{int haxi=now.get_haxi();if(dp[haxi][op]!=-inf) return dp[haxi][op];if(!now.jd()) return now.get();//直接出结果if(op)//取max,最大化收益 {for(int i=1;i<=3;++i){for(int j=1;j<=3;++j){if(now.tr[i][j]=='.')//开始 {ty nex=now;nex.tr[i][j]='O';dp[haxi][op]=max(dp[haxi][op],dfs(nex,0));}	}	}	} else{dp[haxi][op]=inf;for(int i=1;i<=3;++i){for(int j=1;j<=3;++j){if(now.tr[i][j]=='.')//开始 {ty nex=now;nex.tr[i][j]='X';dp[haxi][op]=min(dp[haxi][op],dfs(nex,1));}	}}}return dp[haxi][op];
}
void init()
{for(int i=0;i<=100000;++i){for(int j=0;j<=1;++j){dp[i][j]=-inf;}}
}
void solve()
{ll op;cin>>op;ty nd;for(int i=1;i<=3;++i){for(int j=1;j<=3;++j){cin>>nd.tr[i][j];}}cout<>t;while(t--)solve();return 0;
}

相关内容

热门资讯

前端-session、jwt 目录:   (1)session (2&#x...
linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
前缀和与对数器与二分法 1. 前缀和 假设有一个数组,我们想大量频繁的去访问L到R这个区间的和,...
nodejs:本地安装nvm实... 一、背景-使用不同版本node的原因 vue3+ts、nuxt3版本,node...
JAVA集合知识整理 Java集合知识整理 HashMap相关 HashMap的底层数据结构:jdk1.8之...
无刷直流电机介绍及单片机控制实... 无刷直流电机介绍及单片机控制实例前言基本概念优势与劣势使用寿命基本结构使用单片机控制实例电子调速器&...
fwdiary(2) dp2 1.传纸条  AcWing 275. 传纸条 - AcWing 走两条路,走一条最大的...
常用的DOS命令 常用的DOS命令 DOS(Disk Operating System,磁...
<C++> 类和对象(下) 1.const成员函数将const修饰的“成员函数”称之为const成员函数,cons...