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$ 的时候,不仅要考虑先手能否可以胜利,还要考虑先手能否失败。
怎样计算先手能否失败呢?我们将前面叶节点的时候设为必胜(是指一定可以输掉游戏,不是原来的游戏的必胜),那么如果最后根节点是必胜的话,那么他一定可以在走到叶节点为必胜状态,也就是可以输掉。
接着,我们发现先手有四种情况了:可胜可败,只能胜,只能败,不能胜也不能败(由后手控制力)。
可胜可败:发现先手可以操控答案的走向,一定是可以赢的。
 
只能胜:最开始的先手和后手会交替先走,所以现在要看 $k$ 的奇偶,奇就是先手胜,偶就是后者胜。
 
- 只能败:先手每次都会败,也因为每次都败,每次都是先手,所以最后先手败。
 
- 不能胜也不能败:后手操控答案的走向,所以一定是输的。
 
那么,我们就可以写出了最后的判断代码:
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); 	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; }
   |