原题链接

题目大意

现有一个游戏,给出$N \left ( 1 \le N \le 16 \right )$个不含空格的小写字母字符串(长度不超过10且字符串间两两互不相同),两人轮流从中选择字符串,每个字符串只能被选择一次。除第一次选择外,每次选择的字符串首字母必须和上一次选择的字符串的末字母相同。当轮到某人时没有可选的单词就会输掉,同时另一人获胜。

现在假设两人每次的决策都是最优的,问在给定的字符串下先手还是后手能获胜。

题目样例

1
2
3
4
5
6
7
8
9
10
11
12
13
        **Sample Input 1**

6
enum
float
if
modint
takahashi
template

**Sample Output 1**

First
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
        **Sample Input 2**

10
catch
chokudai
class
continue
copy
exec
havoc
intrinsic
static
yucatec

**Sample Output 2**

Second
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
        **Sample Input 3**

16
mnofcmzsdx
lgeowlxuqm
ouimgdjxlo
jhwttcycwl
jbcuioqbsj
mdjfikdwix
jhvdpuxfil
peekycgxco
sbvxszools
xuuqebcrzp
jsciwvdqzl
obblxzjhco
ptobhnpfpo
muizaqtpgx
jtgjnbtzcl
sivwidaszs

**Sample Output 3**

First

题解

由$\left ( 1 \le N \le 16 \right )$想到可以将每个单词的使用情况以状态压缩的形式表示,这里假设二进制位上1对应单词还没有用过,0表示已被选过,用一个int变量来表示目前还能使用的单词的集合。

经分析,显然所有单词都被选过的状态为必败状态,作为动态规划的初状态,这里将必败设为0。

在游戏的玩家必定做出最优决策的情况下,可以把游戏拆分成各个局面来分析。

一个局面是必胜局面的充要条件是该局面存在将对手引入必败局面的合法决策。

而必败局面的所有合法决策都只能将对手引入必胜局面,或者本身到这就败了。

状态定义和转移

$dp[i][j]$表示当前字符串使用情况状态压缩为i,上一个末字母是j的局面,1为必胜,0为必败。

显然$dp[0][j]=0 \qquad (字符a \le j \le 字符z)$

对于局面$dp[i][j]$,只要有一个游戏推进顺序在其之后的局面是必败的,它就是必胜的,否则必败。

按与游戏推进相反的方向DP,最后检查游戏开始时是否为必胜局面即可判断谁能获胜。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<iostream>
#include<algorithm>
#include<cstring>
#include<vector>
#define fastIO ios_base::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
typedef pair<int,int> PII;

// https://atcoder.jp/contests/abc278/tasks/abc278_f
// 值得仔细思考二人博弈决策的过程

int main(){
fastIO

int n;
cin>>n;

vector<PII> words;

for(int i=0;i<n;i++){ // 将每个字符串的首末字母处理成数字,方便DP过程取位数
string s;
cin>>s;
words.push_back({s.front()-'a',s.back()-'a'});
}

vector<int> dp(1<<n); // 这里dp为一维数组,第二维(字母)包含在dp[i]存储的int变量中,节省空间

for(int i=1;i<1<<n;i++) // 二进制位上1表示未使用对应编号的单词
for(int j=0;j<n;j++) // 枚举单词编号
if(1&i>>j) // 如果当前单词在集合中
dp[i] |=(1&~dp[i^1<<j]>>words[j].second)<<words[j].first;
// 如果i状态使用了单词j的游戏后续状态i^1<<j是必败状态,则将i对应好首末字母设置为必胜状态
// (1&~dp[i^1<<j]>>words[j].second)表示上面题解方程中的(dp[i^1<<j][words[j].second]==0)
// words[j].first就是i转移到后续状态i^1<<j用的“末字母”编号
// words[j].second就是i^1<<j转移到后续状态的“末字母”编号

cout<<(dp.back()?"First":"Second")<<endl; // 2^n-1初始状态下先手有无必赢的决策路径可走

return 0;
}