CF455B

SG 函数的简单题目。

1. 题意

两人交替在开始为空字符串的后面加入字符,要求必须随时为给定的字符串集的某一个串的前缀。将会进行 $k$ 次游戏,上一局输的人作为下一局的先手,最后一局胜利的人获得最终的胜利。在足够聪明的情况下,问第一局的先手是否会赢。

字符串的总个数 $\leq10^5$,字符串的总字符数 $\leq10^5$,$k\leq10^9$。

2. 思路

首先,我们可以转化为一个在 Trie 上走,不能走者输的情况。

考虑 $k=1$ 的情况:这不就是一个 SG 函数的应用吗?

遍历一遍 Trie,叶节点为先手必败,如果一个节点的某个儿子是先手必败,那么该节点先手必胜,否则先手必败。

那么,我们可以简单的写出代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void get_trie(int u)
{
bool has_son = 0;
for (int i = 0; i < 26; ++ i)
if (son[u][i]) has_son = 1, get_trie(son[u][i]);
if (!has_son){
sg[u] = 0;
return;
}
for (int i = 0; i < 26; ++ i)
if (son[u][i] && !sg[son[u][i]])
{
sg[u] = true;
return;
}
sg[u] = false;
return;
}

接着,我们考虑 $k=2$ 的情况。

如果先手 $k=1$ 是一定会胜而不可能输的(注意,这里指即使先手想输也不可能),那么,先手一定会在前一局的时候尽一切可能去输(感觉有点不合常理),这样他就会获得下一局的先手,而获得最后的胜利。

所以,我们在 $k>1$ 的时候,不仅要考虑先手能否可以胜利,还要考虑先手能否失败。

怎样计算先手能否失败呢?我们将前面叶节点的时候设为必胜(是指一定可以输掉游戏,不是原来的游戏的必胜),那么如果最后根节点是必胜的话,那么他一定可以在走到叶节点为必胜状态,也就是可以输掉。

接着,我们发现先手有四种情况了:可胜可败,只能胜,只能败,不能胜也不能败(由后手控制力)。

  1. 可胜可败:发现先手可以操控答案的走向,一定是可以赢的。

  2. 只能胜:最开始的先手和后手会交替先走,所以现在要看 $k$ 的奇偶,奇就是先手胜,偶就是后者胜。

  3. 只能败:先手每次都会败,也因为每次都败,每次都是先手,所以最后先手败。
  4. 不能胜也不能败:后手操控答案的走向,所以一定是输的。

那么,我们就可以写出了最后的判断代码:

1
2
3
4
if ((able_win && able_lose)) puts("First");
else if (able_win) puts(k & 1 ? "First" : "Second");
else if (able_lose) puts("Second");
else puts("Second");

3. Code

前面基本已经展示了,这里给一个完整代码。

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;

const int N = 1e5 + 10;
int son[N][26], rt = 1, tot = 1;
int id[N], n, k, flag, sg[N], able_lose, able_win;
char str[N];

void insert(int i, char *s)
{
int u = rt;
for (int i = 1; s[i]; ++ i)
{
if (!son[u][s[i] - 'a']) son[u][s[i] - 'a'] = ++ tot;
u = son[u][s[i] - 'a'];
}
id[i] = u;
}

void get_trie(int u)
{
bool has_son = 0;
for (int i = 0; i < 26; ++ i)
if (son[u][i]) has_son = 1, get_trie(son[u][i]);
if (!has_son){
sg[u] = flag;
return;
}
for (int i = 0; i < 26; ++ i)
if (son[u][i] && !sg[son[u][i]])
{
sg[u] = true;
return;
}
sg[u] = false;
return;
}

int main()
{
cin >> n >> k;
for (int i = 1; i <= n; ++ i)
{
scanf("%s", str + 1);
insert(i, str);
}
flag = 1, get_trie(1);//flag 是指叶节点是胜还是败
able_lose = sg[1];
flag = 0, get_trie(1);
able_win = sg[1];
if ((able_win && able_lose)) puts("First");
else if (able_win) puts(k & 1 ? "First" : "Second");
else if (able_lose) puts("Second");
else puts("Second");
return 0;
}