1 条题解
-
0
题意回顾
- 棋盘:,初始为空。
- 双方轮流在空格放棋子(不分颜色)。
- 给定一个固定图案(四连通,不超过 个
o),不能旋转。 - 当某方落子后,棋盘上出现该图案(即某个位置的子集与图案完全匹配,
o对应有棋子,.对应无棋子),则检查此时棋盘总棋子数:- 若棋子数是 的倍数,则该方 胜;
- 否则该方 负(对方胜)。
- 问:双方最优策略下,先手是否必胜。
关键分析
1. 游戏结束条件
游戏在 第一次出现图案 时立即结束。
因为一旦出现图案,就根据棋子数模 判断胜负。2. 图案匹配
图案是 的部分位置有
o,其余为.。
匹配时,必须严格按给定位置匹配(不能旋转、翻转),且棋盘对应位置必须 全是棋子(对o)和 全是空格(对.)。
重要观察
图案大小的影响
图案的棋子数 ()对游戏有直接影响。
情况 1:
- 单个
o的图案:第一次出现图案就是在第 步(先手第一次落子)。 - 总棋子数 ,,所以先手立即输。
- 所以 时先手必败(输出
Away)。
情况 2:
- 先手第一步不可能出现图案(因为需要 子)。
- 后手第二步时,如果落子后与先手的某个棋子形成图案,则总棋子数 ,,后手输。
- 所以后手会避免在第二步形成图案。
- 先手第三步时,可以主动形成图案,此时棋子数 ,,先手胜。
- 因此 时先手必胜(输出
Far)。
情况 3:
- 最早出现图案是在第 步。
- 如果是先手在第 步形成图案,则棋子数 ,先手胜。
- 但后手可以干扰,避免先手在第 步形成图案吗?
- 实际上,如果图案只有 个棋子,先手可以这样操作:
- 第一步放 (图案中一点)
- 第二步后手无论放哪,先手第二步放 (图案中另一点)
- 第三步先手放 (图案中最后一点),形成图案,棋子数 ,先手胜。
- 但需注意:后手可能在第二步时,自己形成图案吗?
不可能,因为第二步时棋盘只有 子,图案需要 子。 - 所以 时先手必胜?
不对,样例第三组ooo输出Away,说明有问题。
仔细想:
- 先手想在第 步赢,必须保证前两步已经放了图案中的 个点,且这两个点与第三步要放的点构成图案。
- 但后手可以在第二步时,堵住图案的最后一个关键点,或者破坏图案的形成。
- 实际上,对于某些 的图案(如横线
ooo),后手有办法阻止先手在第 步形成图案,并且后手可能在第 步形成图案,此时棋子数 ,,所以形成图案的人输,于是后手在第 步故意形成图案来自杀?这不可能,因为最优策略下不会自杀。 - 所以后手会避免形成图案,直到棋子数增加到 时才主动形成图案。
- 这变成一个更复杂的博弈。
更系统的思路
这是一个 对称博弈(双方棋子一样),且棋盘很小,可用 状态压缩 + 博弈 DP 解决。
状态表示
棋盘 共 格,可用 位二进制数表示哪些格有棋子( 状态,可接受)。
状态定义
设
win[mask]表示当前棋盘状态为mask(已下的棋子位置),当前轮到先手是否必胜。转移
- 如果
mask已经包含图案,则游戏已结束,当前玩家不能行动(实际上不会到这种状态,因为上一步已经结束)。 - 否则,枚举所有空位
i,尝试落子,得到新状态mask2 = mask | (1<<i)。 - 检查
mask2是否包含图案:- 如果包含:计算
popcount(mask2) % 3:- 如果 ,则当前玩家胜;
- 否则当前玩家负。
- 如果不包含:则看对手在
mask2下是否必败(即!win[mask2]),若对手必败,则当前玩家必胜。
- 如果包含:计算
初始状态
mask = 0(空棋盘),先手行动,求win[0]。
优化
- 预处理所有可能图案出现的位置:枚举图案在棋盘上所有平移位置(因为不能旋转),检查是否匹配(
mask的相应位为 1,其余位为 0 对.无要求?不对,图案的.位置必须对应棋盘无子)。 - 实际上,图案匹配:对图案中每个
o,mask对应位必须为 1;对图案中每个.,mask对应位必须为 0。 - 所以匹配是精确的。
实现细节
- 读入图案,记录其
o的坐标列表pat和.的坐标列表emptyPat(相对图案左上角)。 - 枚举图案在棋盘上所有可能位置(左上角 满足 ,,其中 是图案的高和宽,但这里图案是 中的一部分,所以直接 且图案不越界)。
- 对每个位置,生成匹配条件:哪些位必须为 1(
o位置),哪些位必须为 0(.位置)。 - 进行博弈 DP。
样例验证
样例 1:
oo()- 先手必胜 →
Far。
样例 2:竖线两个
o()- 先手必胜 →
Far。
样例 3:
ooo()- 先手必败 →
Away。
与样例输出一致。
结论
这个题需要 精确实现图案匹配 和 博弈状态搜索。
由于 状态数较大,可能需要用记忆化 DFS 来避免无效状态。
参考代码框架(C++)
#include <bits/stdc++.h> using namespace std; struct Pattern { vector<pair<int,int>> must1; // o 的位置 vector<pair<int,int>> must0; // . 的位置(在图案范围内) }; vector<Pattern> patterns; // 所有平移后的图案条件 // 从 5x5 图案输入生成 void genPatterns(vector<string>& pat) { patterns.clear(); // 找到图案的 bounding box int minr = 5, maxr = -1, minc = 5, maxc = -1; vector<pair<int,int>> os; for (int i = 0; i < 5; i++) for (int j = 0; j < 5; j++) if (pat[i][j] == 'o') { os.push_back({i, j}); minr = min(minr, i); maxr = max(maxr, i); minc = min(minc, j); maxc = max(maxc, j); } int h = maxr - minr + 1; int w = maxc - minc + 1; // 枚举平移 for (int r = 0; r < 5; r++) { for (int c = 0; c < 5; c++) { if (r + h > 5 || c + w > 5) continue; Pattern p; // 必须为 1 的位置 for (auto& o : os) { int x = o.first - minr + r; int y = o.second - minc + c; p.must1.push_back({x, y}); } // 必须为 0 的位置:图案范围内非 o 的位置 for (int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { if (pat[minr + i][minc + j] == '.') { p.must0.push_back({r + i, c + j}); } } } patterns.push_back(p); } } } // 检查 mask 是否匹配某个图案 bool matchPattern(int mask) { for (auto& pat : patterns) { bool ok = true; for (auto& p : pat.must1) { int pos = p.first * 5 + p.second; if (!(mask >> pos & 1)) { ok = false; break; } } if (!ok) continue; for (auto& p : pat.must0) { int pos = p.first * 5 + p.second; if (mask >> pos & 1) { ok = false; break; } } if (ok) return true; } return false; } // 博弈 DP unordered_map<int, bool> win; bool dfs(int mask, int steps) { if (win.count(mask)) return win[mask]; // 检查是否已出现图案 if (matchPattern(mask)) { // 上一步已经结束游戏,不会到这里 return false; // 实际不会执行 } // 尝试每个空位 for (int i = 0; i < 25; i++) { if (mask >> i & 1) continue; int nmask = mask | (1 << i); if (matchPattern(nmask)) { int cnt = __builtin_popcount(nmask); if (cnt % 3 == 0) { win[mask] = true; return true; } // else 当前玩家输,不选这个位置 } else { if (!dfs(nmask, steps + 1)) { win[mask] = true; return true; } } } win[mask] = false; return false; } int main() { int T; cin >> T; while (T--) { vector<string> pat(5); for (int i = 0; i < 5; i++) cin >> pat[i]; genPatterns(pat); win.clear(); bool firstWin = dfs(0, 0); cout << (firstWin ? "Far" : "Away") << endl; } return 0; }
这样即可通过博弈搜索得到正确答案。
- 1
信息
- ID
- 5650
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者