P14549. [IO 2024 #3] 原始象棋
前言
据说正解是搜子打表。
但是分讨真的能过!
by Conan15 & Union_Find。
对着题目脑电波了半天才理解操作不能不动,原因是:
保证两个棋子位于不同的格子,且每个棋子都能走某一步棋。
Solution
不难特判掉一些简单和平凡的 Case。
- 先手一步杀后手。
- 两个象的 \(x+y\) 奇偶性不同(即永远无法相遇)。
然后凭直觉,棋盘越大活动范围就越大,所以猜测当 \(n,m \geq k\)(\(k\) 是某个定值)的时候一定是平局。
Case 1
考虑手玩小数据:\(n=2\) 或 \(m=2\)。
- 如果先手是车后手是象,必定可以躲开后手的攻击,所以
WIN。 - 如果先手是象后手是车,由于判了一步杀,所以它必定无法躲开后手的攻击,答案是
LOSE。 - 如果两者都是车,可以二人转,答案是
DRAW。 - 如果两者都是象,答案与它们相遇的操作步数(中间距离)相关,奇数则
WIN,偶数则LOSE。
这些观察都是显然的。
Case2
由于象的活动范围相较车限制更强,所以考虑一般的两个象如何处理。注意此时它们奇偶性是相同的。
下文钦定 \(n \leq m\)。
观察到 \(n,m \geq 4\) 的时候空间足够大,可以让两个象二人转,所以我们讨论 \(n=3\) 的情况。
Case2.1
基于如下观察:

先手 LOSE,因为下一步被限定死了,被二步杀。
拓展一下,这种情况只要出现在 \(3 \times m\) 的两端,先手就寄了。
Case2.2
假定接下来蓝色为先手,红色为后手。

先手往 \((2,3)\) 逼近一格,后手无论走哪边,下一步先手都可以击杀后手,三步杀从而 WIN。
同理这种情况也可以出现在网格左右两边。
Case3
经过上述 Case,如果两个棋子类型相同,则必定可以通过二人转达到 DRAW 的局面。
接下来两个棋子类型不同。
Case4
循序渐进!考虑 \(n=m=3\) 的情况。
我们注意到车需要能攻击象的所有可到达位置,才能获得胜利。我们称这种局面为车“压住”象。
Case4.1
考虑 \(3 \times 3\) 网格先手为象。
Case4.1.1
当象在正中间,它可以在主副对角线任意移动,无论车如何牵制都不可能压住象,所以答案是 DRAW。
Case4.1.2
考虑象在某个角上,则后手有两种位置可以压住象。

以先手在 \((1,1)\) 为例,则后手有 \((2,3)\) 和 \((3,2)\) 两种位置可以压住象的所有移动位置。
此时先手 LOSE。同理其它三个角也分别有两种位置可以压住,不再赘述。
Case4.1.3
考虑象在其余位置(\(x+y\) 为奇数的位置)上。
此时象无法运动到中间位置,那么车只要占据中间位置象就必死,答案为 LOSE。
Case4.1.4
其余情况都是二人转,DRAW。
Case4.2
还是 \(3 \times 3\) 网格,考虑先手为车。
Case4.2.1
和之前一样,象在中间是 DRAW。
Case4.2.2
同样可以通过象的位置推断出车到哪些位置可以压住象。
只要判断车是否能通过一步移动到达这些位置之一即可,是则 WIN,不是则 DRAW。
这可以写个函数判断,详见代码。
Case4.2.3
其余情况 DRAW。
Case5
更一般的 \(n=3\) 情况怎么写呢?
由于象的活动范围被严格限制,我们猜测象一定输车一定赢。
这是对的,如果车先手可以先撤一步让象吃不到自己。
接着考虑车以如下策略把象往某一边赶(设先手是象,后手是车):
先手往左上方走:
- 先手 \((1,3)\),后手 \((2,5)\)。
- 先手 \((3,1)\),后手 \((2,3)\)。
先手往右下方走:
- 先手 \((3,5)\),后手 \((2,3)\)。
- 先手被压住了,这样对先手不优,所以先手一定往左上方走。
当先手不断选择往左上奏,走到边界必定被压住,因此象必败车必胜。
Case6
还是 WA 了,怎么回事呢。
那就继续分讨:考虑 \(n=m=4\) 的情况。
Case6.1
先手是象。
Case6.1.1
先手是象,和 Case4 类似的,如果在中间 \(2 \times 2\) 四格则车压不住它,答案是 DRAW。
同理,如果在对角线上也压不住。因为象一次可以飞三个点,车压不住三个点,答案也是 DRAW。
Case6.1.2
通过手玩,我们可以发现如下情况:

如果初始状态是象先手,车在 R 位置,无论象走哪里下一步都会被吃,我们称它为二步杀。
考虑如果车改为 R2 位置,象为了第一步不被吃只能走到 \((1,2)\),然后车只要左移一步就恢复了二步杀的局面,因此象也是必败。
同理的,对于在某一边缘的中间两格的象,都有两个位置放车使得它 LOSE,另外 \(7\) 种情况是对称的,故不再赘述。
Case6.1.3
又 WA 了怎么回事呢。
通过下载数据,我们发现:

这种情况比较难,但是如果车走到 \((3,4)\) 并且象仍在 \((1,2)\),下一步象先手,那么车就赢了。
原因是:

象肯定不能走 \((2,3)\),否则会被二步杀。
象如果走 \((2,1)\),车左移到 \((4,2)\) 接一个二步杀;象如果走 \((3,4)\),车上移到 \((1,3)\) 接一个二步杀,因此车必胜,我们称这种为“三步杀”。
考虑怎么在不改变象的位置的前提下将车从 \((1,3)\) 移动到 \((4,3)\),并且下一步还是象先手。
若象走了 \((2,1)\),那么车压 \((3,3)\),接下来象只能退回 \((1,2)\),车借机继续往下到 \((3,4)\),接一个三步杀。
若象走了 \((3,4)\),车压 \((3,3)\),象退回 \((1,2)\),车继续往下到 \((3,4)\),接一个三步杀。
因此这种情况先手象是 LOSE 的。
Case6.1.4
通过换一个号下载数据,有如下 hack:

此时先手象显然只能走 \((3,4)\),考虑让车向左到 \((1,2)\),接着局面长这样(依然象先手):

哎你会发现这和 Case6.1.3 的第二张图是同一个东西啊,先手象 LOSE。
总结一下 Case6.1.3 和 Case6.1.4 就是:如果象在边缘的中间两格,车和它相邻且不在四角,那么先手象必败。
Case6.1.5
剩下情况二人转平局。
Case6.2
先手改为车,其实是同理的。
Case6.2.1
如果后手象在中间四格或角落,车压不住,答案是 DRAW。
Case6.2.2
可以把 Case6.1 打包成一个 check 函数,然后遍历车可以走的所有位置扔进去 check,有一个能赢就是 WIN,否则是 DRAW 因为显然车不会输。
Case7
既然讨论了 \(4 \times 4\),当然也要讨论 \(n=4\) 时一般的 \(4 \times m\) 网格怎么决策。
Case7.1
先手是象。
Case7.1.1
在中间 \(2 \times (m-2)\) 的区域内依然平局。
同理只要在四个角也是平局。
Case7.1.2
其余情况都是把 \(4 \times 4\) 的情况放在 \(4 \times m\) 的左右两侧,改一下下标是一样的。
但是注意这里只存在二步杀,如果二步以内车杀不掉象那么象就往网格中间跑了。
Case7.2
先手是车,处理方法和 Case6 类似,去遍历车的所有可到达位置。
Code
代码写丑了,其实所有象做先手都可以封装成函数,车先手的时候直接调用即可。
然而我们补题的时候红温了,写的是暴力分讨。
代码大致是按题解分讨顺序写的,但是没有写注释所以可读性极差,谨慎食用。
#include <bits/stdc++.h>
using namespace std;
int T;
int n, m, x, y, xx, yy;
char c, cc;
inline void check(int sx, int sy) {
puts((sx == xx && sy == yy) ? "LOSE" : "DRAW");
}
inline void check(int sx, int sy, int sxx, int syy) {
if (sx == x && sy == y) puts("DRAW");
else if (sxx == x && syy == y) puts("DRAW");
else if (sx == x || sy == y) puts("WIN");
else if (sxx == x || syy == y) puts("WIN");
else puts("DRAW");
}
inline void checkk(int sx, int sy) {
if (sx == x && sy == y) puts("DRAW");
else if (sx == x || sy == y) puts("WIN");
else puts("DRAW");
}
inline void check4(int sx, int sy, int sxx, int syy){
puts((sx == xx && sy == yy) || (sxx == xx && syy == yy) ? "LOSE" : "DRAW");
}
inline void checkk4(int sx, int sy, int sxx, int syy) {
if (sx == x && sy == y) puts("DRAW");
else if (sxx == x && syy == y) puts("DRAW");
else if (sx == x || sy == y) puts("WIN");
else if (sxx == x || syy == y) puts("WIN");
else puts("DRAW");
}
inline bool corner(int x, int y){ return (x == 1 && y == 1) || (x == 1 && y == 4) || (x == 4 && y == 1) || (x == 4 && y == 4); }
inline bool checker4(int x, int y, int xx, int yy){
if (y - x == yy - xx || x + y == xx + yy) return 0;
if ((x == 1 || x == 4 || y == 1 || y == 4) && abs(xx - x) + abs(yy - y) == 1 && !corner(xx, yy))
return 1;
if (xx > 1 && xx < n && yy > 1 && yy < m){
if ((x == 1 || x == 4 || y == 1 || y == 4) && abs(5 - xx - x) + abs(5 - yy - y) == 1)
return 1;
}
return 0;
}
void solve() {
scanf("\n %d%d", &n, &m);
scanf("\n %d%d %c\n %d%d %c", &x, &y, &c, &xx, &yy, &cc);
if (n == 1 || m == 1) {
if (c == 'R') puts("WIN");
else {
if (cc == 'R') puts("LOSE");
else puts("DRAW");
}
return;
}
if (c == 'R') {
if (x == xx || y == yy) return puts("WIN"), void();
} else {
if (y - x == yy - xx || x + y == xx + yy) return puts("WIN"), void();
}
// can't meet
if (c == 'B' && cc == 'B' && ((x + y) & 1) != ((xx + yy) & 1)) return puts("DRAW"), void();
if (n == 2 || m == 2) {
if (c == 'R' && cc == 'B') return puts("WIN"), void();
else if (c == 'B' && cc == 'R') return puts("LOSE"), void();
else if (c == 'B' && cc == 'B') {
if (((x + y) & 1) ^ ((xx + yy) & 1)) return puts("DRAW"), void();
int dis = abs(x - xx);
return puts((dis & 1) ? "WIN" : "LOSE"), void();
} else if (c == 'R' && cc == 'R') {
if (n == 2 && m == 2) return puts("LOSE"), void();
return puts("DRAW"), void();
}
}
if (c == 'B' && cc == 'B') {
if (n == 3 && x == 2 && xx == 2 && ((y == 1 && yy == 3) || (y == m && yy == m - 2))) return puts("LOSE"), void();
if (m == 3 && y == 2 && yy == 2 && ((x == 1 && xx == 3) || (x == n && xx == n - 2))) return puts("LOSE"), void();
if (n == 3 && (x == 1 || x == 3) && y == 4 && xx == 2 && yy == 1) return puts("WIN"), void();
if (n == 3 && (x == 1 || x == 3) && y == m - 3 && xx == 2 && yy == m) return puts("WIN"), void();
if (m == 3 && (y == 1 || y == 3) && x == 4 && yy == 2 && xx == 1) return puts("WIN"), void();
if (m == 3 && (y == 1 || y == 3) && x == n - 3 && yy == 2 && xx == n) return puts("WIN"), void();
}
if (c == cc) return puts("DRAW"), void();
if (n == 3 && m == 3){
if (c == 'B'){
if (x == 2 && y == 2) return puts("DRAW"), void();
if (x == 1 && y == 1 && xx == 3 && yy == 2) return puts("LOSE"), void();
if (x == 1 && y == 3 && xx == 3 && yy == 2) return puts("LOSE"), void();
if (x == 3 && y == 1 && xx == 1 && yy == 2) return puts("LOSE"), void();
if (x == 3 && y == 3 && xx == 1 && yy == 2) return puts("LOSE"), void();
if (x == 1 && y == 1 && xx == 2 && yy == 3) return puts("LOSE"), void();
if (x == 1 && y == 3 && xx == 2 && yy == 1) return puts("LOSE"), void();
if (x == 3 && y == 1 && xx == 2 && yy == 3) return puts("LOSE"), void();
if (x == 3 && y == 3 && xx == 2 && yy == 1) return puts("LOSE"), void();
if ((x + y) & 1) return puts("LOSE"), void();
return puts("DRAW"), void();
} else {
if (xx == 2 && yy == 2) return puts("DRAW"), void();
if (xx == 1 && yy == 1) return check(3, 2, 2, 3), void();
if (xx == 1 && yy == 3) return check(3, 2, 2, 1), void();
if (xx == 3 && yy == 1) return check(1, 2, 2, 3), void();
if (xx == 3 && yy == 3) return check(1, 2, 2, 1), void();
if ((xx + yy) & 1) return puts("WIN"), void();
return puts("DRAW"), void();
}
}
if (n == 3 || m == 3) {
if (c == 'B') puts("LOSE");
else puts("WIN");
return ;
}
if (n == 4 && m == 4){
if (c == 'B') {
if (x > 1 && x < n && y > 1 && y < m) return puts("DRAW"), void();
if (corner(x, y)) return puts("DRAW"), void();
if (checker4(x, y, xx, yy)) return puts("LOSE"), void();
if (x == 1 && y == 2) return check4(2, 4, 4, 3), void();
if (x == 1 && y == 3) return check4(2, 1, 4, 2), void();
if (x == 2 && y == 1) return check4(4, 2, 3, 4), void();
if (x == 2 && y == 4) return check4(4, 3, 3, 1), void();
if (x == 3 && y == 1) return check4(1, 2, 2, 4), void();
if (x == 3 && y == 4) return check4(1, 3, 2, 1), void();
if (x == 4 && y == 2) return check4(3, 4, 1, 3), void();
if (x == 4 && y == 3) return check4(3, 1, 1, 2), void();
} else {
if (xx > 1 && xx < n && yy > 1 && yy < m) return puts("DRAW"), void();
if (corner(xx, yy)) return puts("DRAW"), void();
for (int i = 1; i <= 4; i++) if (i != x && checker4(xx, yy, i, y))
return puts("WIN"), void();
for (int i = 1; i <= 4; i++) if (i != y && checker4(xx, yy, x, i))
return puts("WIN"), void();
if (xx == 1 && yy == 2) return checkk4(2, 4, 4, 3), void();
if (xx == 1 && yy == 3) return checkk4(2, 1, 4, 2), void();
if (xx == 2 && yy == 1) return checkk4(4, 2, 3, 4), void();
if (xx == 2 && yy == 4) return checkk4(4, 3, 3, 1), void();
if (xx == 3 && yy == 1) return checkk4(1, 2, 2, 4), void();
if (xx == 3 && yy == 4) return checkk4(1, 3, 2, 1), void();
if (xx == 4 && yy == 2) return checkk4(3, 4, 1, 3), void();
if (xx == 4 && yy == 3) return checkk4(3, 1, 1, 2), void();
}
}
if (n == 4 || m == 4){
if (c == 'B') {
if (x > 1 && x < n && y > 1 && y < m) return puts("DRAW"), void();
if ((x == 1 || x == n) && (m > 4 || y == 1 || y == m)) return puts("DRAW"), void();
if ((y == 1 || y == m) && (n > 4 || x == 1 || x == n)) return puts("DRAW"), void();
if (x == 1 && y == 2 && m == 4) return check(2, 4), void();
if (x == 1 && y == 3 && m == 4) return check(2, 1), void();
if (x == 2 && y == 1 && n == 4) return check(4, 2), void();
if (x == 2 && y == m && n == 4) return check(4, m - 1), void();
if (x == 3 && y == 1 && n == 4) return check(1, 2), void();
if (x == 3 && y == m && n == 4) return check(1, m - 1), void();
if (x == n && y == 2 && m == 4) return check(n - 1, 4), void();
if (x == n && y == 3 && m == 4) return check(n - 1, 1), void();
} else {
if (xx > 1 && xx < n && yy > 1 && yy < m) return puts("DRAW"), void();
if ((xx == 1 || xx == n) && (m > 4 || yy == 1 || yy == m)) return puts("DRAW"), void();
if ((yy == 1 || yy == m) && (n > 4 || xx == 1 || xx == n)) return puts("DRAW"), void();
if (xx == 1 && yy == 2 && m == 4) return checkk(2, 4), void();
if (xx == 1 && yy == 3 && m == 4) return checkk(2, 1), void();
if (xx == 2 && yy == 1 && n == 4) return checkk(4, 2), void();
if (xx == 2 && yy == m && n == 4) return checkk(4, m - 1), void();
if (xx == 3 && yy == 1 && n == 4) return checkk(1, 2), void();
if (xx == 3 && yy == m && n == 4) return checkk(1, m - 1), void();
if (xx == n && yy == 2 && m == 4) return checkk(n - 1, 4), void();
if (xx == n && yy == 3 && m == 4) return checkk(n - 1, 1), void();
}
}
puts("DRAW");
}
int main() {
scanf("%d", &T);
while (T--) solve();
return 0;
}

浙公网安备 33010602011771号