ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

洛谷P4576题解

2022-06-11 11:00:35  阅读:134  来源: 互联网

标签:状态 洛谷 白子 P4576 题解 include 步数 dir 黑子


原题

P4576 [CQOI2013]棋盘游戏


思路概述

题意分析

给定一个大小为 \(n×n\) 的棋盘和白子与黑子坐标 \((x_1,y_1),(x_2,y_2)\) 。定义白子与黑子的移动操作:白子每回合可以向四个方向移动一格;黑子每回合可以向四个方向移动一或两格。规定白子先出棋。要求求解必胜方并且输出操作步数(满足必胜方尽快取胜,必败方尽量拖延的策略)。

思路分析

一道有点难想并且难写的博弈论+记搜题。

胜负态乍一看不好想,但是玩过类似游戏的读者应该不难想到:在双方拥有相同操作机会的前提下,移动速度快的一方总不会输。当速度慢的一方有机会移动到能吃掉快方的时候,快方必然能靠速度优势甩开一定距离,除了一种特例:快方一开始就在慢方的移动范围内并且慢方先手。

本题在胜负态判断上就应用了类似思路。显而易见地,白子移动速度慢于黑子,但是有先手机会。根据上述结论不难得到本题胜负判断函数。

至于步数统计则采用DFS,但由于状态过多,需要采用记忆化搜索进行一定剪枝,在搜索到游戏结束状态后再回溯求解步数。


算法实现

胜负态判断

引入一个概念(其实跟本题关系并不大):曼哈顿距离。曼哈顿距离指在 \(n\) 维空间中,两个点在所有轴方向上的距离和。例如:在平面上,两个点的曼哈顿距离表示为 \(|x_1-x_2|+|y_1-y_2|\) 。

当黑子白子初始坐标相邻时,白子可以一步吃掉黑子,此时白方必胜,步数为 \(1\) 。

即(此处用 \((x_1,y_1),(x_2,y_2\) 分别表示白子与黑子坐标,函数值为 \(1\) 表示白方必胜,反之必败):

\(F(x_1,y_1,x_2,y_2)=\begin{cases}1,|x_1-x_2|+|y_1-y_2|≤1 \\0,\text{otherwise}\end{cases}\)

关于步数统计之前的一些预处理

由于是棋盘上的DFS,所以需要预处理移动数组。处理方式跟入门的地图搜索题没区别,此处不再冗述。

状态定义与计数思路

一个地图上博弈游戏的状态,首先必须包含点坐标,在本题中即为必须包含白子与黑子坐标 \(x_1,y_1,x_2,y_2\) ;其次,由于轮流出棋,所以状态同样包含当前的出棋方 \(opt\) ;最后,由于需要统计操作步数,所以搜索函数参数传递也需要将当前搜索状态的步数向下传递以及向上回溯。因此,本题的状态即可定义为:

\[f(opt,s,x_1,y_1,x_2,y_2) \]

其中, \(opt\) 为当前出棋方, \(s\) 为从初始状态到当前状态的转移步数。

为方便表述,笔者在此处定义操作 \(move(x_1,x_2,dir,dis)\) 表示坐标在 \((x_1,y_2)\) 的棋子向方向 \(dir(dir∈[0,3])\) 移动 \(dis\) 格后得到的新坐标。

因为已经判定了白方必胜的情况,所以此处只需要用DFS统计黑方胜利的步数。

先从初始状态开始搜索,当搜索到其中两个棋子坐标重合结束该方向的搜索并统计一次答案,再在回溯过程中累加得到黑方获胜所需要的最小步数。

游戏结束的情况判断

特别地,当出现坐标重合时,若当前操作方为黑方,则黑子被吃,即黑方不可能以当前状态获胜,返回一个极大值 \(inf\) 为步数;反之,若当前操作方为白方,则说明黑方在此状态获胜,那么返回步数 \(0\) ,表示在当前状态下只需要 \(0\) 步即可达到黑方获胜的状态。

其他状态的转移

对于当前的一个状态 \(f(opt,s,x_1,y_1,x_2,y_2)\) ,若 \(opt=white\) 则需要尽量拖延步数,则取所有后继状态步数的最大值,即 \(f(white,s,x_1,y_1,x_2,y_2)=max\{f(black,s+1,move(x_1,y_1,dir,1)_x,move(x_1,y_1,dir,1)_y,x_2,y_2)\}\) ;反之,就需要尽快获胜,取所有后继状态步数的最小值,即 \(f(black,s,x_1,y_1,x_2,y_2)=min\{f(white,s+1,x_1,y_1,move(x_2,y_2,dir,dis)_x,move(x_2,y_2,dir,dis)_y)\}(dis∈[1,2])\) 。

关于记忆化剪枝

分析上述算法不难知道,本题的大部分状态会被调用不止一次,所以开一个与状态相对应的数组存储每个状态的步数,若在搜索过程中发现过已经被调用过的状态就直接取值。

关于步数上界

由于是DFS,时间复杂度呈指数增长,所以对于那种两个棋子在秦王绕柱的状态,必然将其舍弃。这个舍弃的标准可以具体为 \(s>3n\) 。关于这个标准,笔者并未找出严格满足 \(ans=3n\) 的情况,但是当两个棋子分别占棋盘对角且 \(n\) 较大时,就会出现 \(ans>2.5n\) 的情况(例如 \(n=19,x_1=1,y_1=1,x_2=19,y_2=19\) 的数据)。

\(n>2.5n\) 情况的具体步骤(笔者手推 \(n=19\) 棋盘)大概是黑子以最优方案靠近白子所在的角,白子原地反复走棋拖延时间,当两者曼哈顿距离小等于 \(2\) 时白子开始沿棋盘较安全的一边逃逸,黑子尝试用速度优势堵截但与白子陷入了短时间的绕圈,最后当 \(s≈50\) 时白子被吃,游戏结束。


AC code

因为个人编码习惯,写了结构体和一大堆申必封装函数,实际运行有点慢,凑合看看罢。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<queue>
#include<set>
#include<ctime>
#define RI register int
using namespace std;
typedef struct
{
	int x,y;
	inline void read(void){cin >> x >> y;return;}/*封装读入*/
	inline bool judge(int lim){return (x>=1 && x<=lim && y>=1 && y<=lim);}/*判定出界*/
}point;
const point dir[4]={(point){1,0},(point){0,1},(point){-1,0},(point){0,-1}};
const int maxn=2e1+10,inf=0x3f3f3f3f; 
point w,b;
int n;
int rec[2][(maxn<<1)+maxn][maxn][maxn][maxn][maxn];
inline int getdis(point x,point y);/*求两点曼哈顿距离*/
inline point move(point x,int dr,int ds);/*移动函数 dr为方向 ds为距离*/
inline int dfs(bool isw,int s,point p,point h);/*深度优先搜索函数*/
int main()
{
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin >> n;
	w.read();b.read();
	if(getdis(w,b)<=1) puts("WHITE 1");/*两点距离为1时 白子只需要一步即可吃掉黑子*/
	else cout << "BLACK " << dfs(1,1,w,b);/*两点距离大于1时 白子不可能获胜*/
	return 0;
}
inline int getdis(point x,point y){return abs(x.x-y.x)+abs(x.y-y.y);}
inline point move(point x,int dr,int ds){return (point){x.x+dir[dr].x*ds,x.y+dir[dr].y*ds};}
inline int dfs(bool isw,int s,point p,point h)
{
	if(s>(n<<1)+n) rec[isw][s][p.x][p.y][h.x][h.y]=inf;/*步数最多为3n(构造方法详见题解) 超过3n表示无解*/
	else if(!getdis(p,h)) rec[isw][s][p.x][p.y][h.x][h.y]=(isw)?0:inf;/*黑/白子被吃 判定游戏结束 开始回溯计算步数*/
	else if(!rec[isw][s][p.x][p.y][h.x][h.y])/*记忆化剪枝*/
	{
		RI ret;
		if(isw)/*白子出棋则尽量拖延步数 取步数最大值*/
		{
			ret=0;
			for(RI i=0;i<4;++i)
				if(move(p,i,1).judge(n))
					ret=max(ret,dfs(isw^1,s+1,move(p,i,1),h));		
		}
		else/*黑子出棋则尽量快速取胜 取步数最小值*/
		{
			ret=inf;
			for(RI i=0;i<4;++i)
				for(RI j=1;j<=2;++j)/*黑子可以选择移动1或2格*/
					if(move(h,i,j).judge(n))
						ret=min(ret,dfs(isw^1,s+1,p,move(h,i,j)));
		}
		rec[isw][s][p.x][p.y][h.x][h.y]=ret+1;/*当前的操作也算一步 所以返回ret+1*/
	}
	return rec[isw][s][p.x][p.y][h.x][h.y];
}

标签:状态,洛谷,白子,P4576,题解,include,步数,dir,黑子
来源: https://www.cnblogs.com/frkblog/p/16365377.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有