填字母游戏
问题背景
小明沉迷 LOL。K 大师与他先玩一个"在空格中填字母"的小游戏:
两人轮流在一行格子中,把某个空格替换为 L
或 O
。谁先在整行字符串中形成子串 "LOL" ,谁就获胜 ;若所有格子填满仍无法形成 "LOL",则平局。
游戏规则
- 初始给定一行由
L
、O
和*
(表示空格)组成的字符串。 - 两名玩家轮流行动,每回合:
- 选择任意一个
*
,将其替换为L
或O
。
- 选择任意一个
- 判定:
- 任何时刻,一旦行内出现连续的 "LOL",当前行动的玩家立刻获胜;
- 若所有
*
均被替换完仍未出现 "LOL",则平局。
目标
对每个给定的初始局面,假设小明先手 且双方都采用最优策略,输出小明能达到的最好结果:
1
:先手必胜;0
:先手可保平;-1
:先手必败。
输入格式
- 第 1 行:整数
n
(n < 10
),表示有n
个初始局面。 - 接下来的
n
行:每行一个只包含L
、O
、*
的字符串,表示一个初始局面(例如:"******"
:6 个空格;"L****"
:左端一个L
,其后 4 个空格)。
输出格式
- 输出
n
行,每行一个整数,分别对应每个初始局面的最好结果:1
、0
或-1
(含义见上文"目标")。
示例
输入
in
4
***
L**L
L**L***L
L*****L
输出
out
0
-1
1
1
c++代码
cpp
#include<bits/stdc++.h>
using namespace std;
int n;
string s;
unordered_map<string, int> mp;
int result() {
if (mp.find(s) != mp.end()) return mp[s];
if (s.find("LOL") != -1) {mp[s] = -1; return -1;}
if (s.find("*") == -1) {mp[s] = 0; return 0;}
int key = -1;
for (int i = 0; i < s.size(); i++) {
if (s[i] != '*') continue;
s[i] = 'L';
int sym = result();
if (sym == -1) {
s[i] = '*';
mp[s] = 1;
return 1;
}
else if (sym == 0) key = 0;
s[i] = 'O';
sym = result();
if (sym == -1) {
s[i] = '*';
mp[s] = 1;
return 1;
}
else if (sym == 0) key = 0;
s[i] = '*';
}
mp[s] = key;
return key;
}
int main() {
cin >> n;
while (n--) {
cin >> s;
cout << result() << endl;
}
return 0;
}
题目解析
博弈论(递归+回溯)
如果敌方必赢,那么我方必输,如果敌方必输那么我方必赢。否则可以平局。
c++
if (sym == -1) {
return 1;
}
int sym()的返回值表示,当前局面,先下棋的人的输赢。
我们枚举先下棋的人这一步的每一种下法。
cpp
for (int i = 0; i < s.size(); i++) {
if (s[i] != '*') continue;
s[i] = 'L';
//...省略代码
s[i] = '*';.//回溯
//...省略代码
s[i] = 'O';
s[i] = '*';//回溯
}
然后下一步就轮到敌方下。我们再次调用sym函数获得敌方输赢。
cpp
s[i] = 'L';
int sym = result();
if (sym == -1) {
//...
return 1;
}
一直枚举每一种下法,直到已经分出胜负才停止。
如果敌方的每一种返回值都是必赢那么我们必输。
如果敌方只要有一个必输那么我们必赢。
否则可以平局。
记忆化搜索
递归过程中会出现很多重复情况,而每一种情况的返回值都是一样的。
为了不重复走我们已经模拟过的情况,我们用unorder_map把我们走过的每一种情况存储下来。
下次我们遇到重复的情况的时候,直接返回存储的值,而不是再次递归搜索。
cpp
if (mp.find(s) != mp.end()) return mp[s];