每天一道博弈论之“棋盘游戏”(cqoi2013)
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3106
题意:
一个n*n的棋盘(n<=20),在某两个位置上分别有一个白棋和一个黑棋。先手只能移动白棋,后手只能移动黑棋。白棋的移动规则为只能往上下左右某个方向移动一格,黑棋的移动规则为可以往上下左右某个方向移动一格或两格(不能不移)。双方在能赢时会尽量以少的步数赢,不能赢时会尽量拖延。
给你n和两枚棋子的初始位置,问谁最终会赢?最少需要几步?双方都无必胜策略时输出“DRAW”。
题解:
一开始以为这是个可以无尽的游戏,瞬间吓到了=-=。结果看了题解才发现这个游戏是必定有赢家的。为什么呢?
我们简单的模拟一下游戏就会发现后手这个可以移动一格或两格真是占尽了便宜,完全可以完美地把握与对手的距离,使自己立于不败之地。而且他可以通过移动到白棋的某一角,把对手赶到角落。(如移到白棋右上角,把白棋往左下角赶)。
再看一看数据范围,完全可以记忆化搜索。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<cmath> 6 using namespace std; 7 const int N = 20 + 2 ; 8 9 inline int read() { 10 int k = 0 , f = 1 ; char c = getchar() ; 11 for( ; !isdigit(c) ; c = getchar()) 12 if(c == '-') f = -1 ; 13 for( ; isdigit(c) ; c = getchar()) 14 k = k*10 + c-'0' ; 15 return k*f ; 16 } 17 int n, r1, c1, r2, c2 ; int dp[2][N*3][N][N][N][N] ; 18 19 int dfs(int who,int now,int r1,int c1,int r2,int c2) { // 0表示先手,1表示后手 20 if(now > n*3) return n*3 ; 21 if(r1 == r2 && c1 == c2) { 22 return who ? n*3 : 0 ; 23 } 24 if(dp[who][now][r1][c1][r2][c2]) return dp[who][now][r1][c1][r2][c2] ; 25 int res ; 26 if(!who) { 27 res = 0 ; 28 if(r1 > 1) res = max(res,dfs(1,now+1,r1-1,c1,r2,c2)) ; 29 if(r1 < n) res = max(res,dfs(1,now+1,r1+1,c1,r2,c2)) ; 30 if(c1 > 1) res = max(res,dfs(1,now+1,r1,c1-1,r2,c2)) ; 31 if(c1 < n) res = max(res,dfs(1,now+1,r1,c1+1,r2,c2)) ; 32 } else { 33 res = n*3 ; 34 if(r2 > 1) res = min(res,dfs(0,now+1,r1,c1,r2-1,c2)) ; 35 if(r2 > 2) res = min(res,dfs(0,now+1,r1,c1,r2-2,c2)) ; 36 if(r2 < n) res = min(res,dfs(0,now+1,r1,c1,r2+1,c2)) ; 37 if(r2 < n-1) res = min(res,dfs(0,now+1,r1,c1,r2+2,c2)) ; 38 if(c2 > 1) res = min(res,dfs(0,now+1,r1,c1,r2,c2-1)) ; 39 if(c2 > 2) res = min(res,dfs(0,now+1,r1,c1,r2,c2-2)) ; 40 if(c2 < n) res = min(res,dfs(0,now+1,r1,c1,r2,c2+1)) ; 41 if(c2 < n-1) res = min(res,dfs(0,now+1,r1,c1,r2,c2+2)) ; 42 } 43 res ++ ; 44 return dp[who][now][r1][c1][r2][c2] = res ; 45 } 46 47 inline int aabs(int x) { return x > 0 ? x : -x ; } 48 int main() { 49 n = read(), r1 = read(), c1 = read(), r2 = read(), c2 = read() ; 50 if(aabs(r1-r2)+aabs(c1-c2) <= 1) { 51 printf("WHITE 1") ; return 0 ; 52 } 53 int ans = dfs(0,0,r1,c1,r2,c2) ; 54 printf("BLACK %d",ans) ; 55 return 0 ; 56 }
为什么边界是n*3其实我也不是很清楚。不过如果我们算一下空间的话就会发现开3倍已经将近120M了,所以就算我们想再开大也是心有余而力不足了qaq。
不过我还是要提一些自己关于这个n*3的看法。我们上面说过黑棋要是想赢的话只要把白棋往角落里赶就可以了。然后我就想,那最多不应该是n*4吗?
其实不然。当两枚棋子都在靠近右下角的位置,且黑棋横纵坐标都比白棋大时,黑棋完全没必要把白棋往左上角赶。我们完全可以多跳两步绕到白棋左上方,然后把白棋往右下角赶,这样可以少很多步。n*3算是很宽裕的,其实应该只要比n*2大一些即可。
(如果有大佬能想出严格的证明请在评论区提出。十分感谢♪)