题解:P16790 [蓝桥杯 2026 国 A] 魔法前缀

· · 题解

题意简述

给定若干个小写字符串。 取出它们所有不同的非空前缀。

每次可以选择一个现存前缀, 并删除所有以它为前缀的字符串。 两人轮流操作,无法操作的人失败。

判断先手是否必胜。

解题思路

所有不同前缀组成一棵字典树。 空串是树根,但不属于可选前缀。

选择前缀 P, 等价于删除对应节点及其整棵子树。 也可以理解为切断它与父节点之间的边。

下面求这种树上游戏的 SG 值。

先证明一个常用结论。 设游戏 G 的 SG 值为 x。 在它上方接一条可以直接切断的新边, 得到的新游戏记为 G',则:

\operatorname{SG}(G')=x+1

切断新边后,SG 值为 0。 若不切新边, 则要在原游戏 G 内进行一步操作。

根据 x 的定义,

但不包含 $x$。 对规模更小的后继局面使用同一结论。 这些后继接上新边后, SG 值分别在原值上加一。 因此,$G'$ 的后继包含 $0$ 到 $x$, 但不包含 $x+1$。 取 $operatorname{mex}$ 后恰好得到 $x+1$。 回到字典树。 根节点的每棵儿子子树互不影响, 它们是若干个独立的子游戏。 设 $f_u$ 表示保留节点 $u$, 只允许删除其下方子树时的 SG 值。 对每个儿子 $v$, 连接 $u,v$ 的边为它额外增加一层,故: $$ f_u=\bigoplus_{v\in\operatorname{son}(u)}(f_v+1) $$ 整局游戏的 SG 值就是空串根节点的 $f$。 它非零时先手必胜,否则后手必胜。 代码没有按字符逐个查找字典树儿子。 先将所有原字符串按字典序排序。 相邻字符串的最长公共前缀已经建好。 当前字符串超出公共前缀的部分, 才会产生一条新的节点链。 用 $path_d$ 记录上一字符串长度为 $d$ 的前缀节点。 求出相邻字符串的最长公共前缀长度 $p$ 后, 保留 $path_0$ 到 $path_p$, 再从深度 $p+1$ 起建立新节点。 相同字符串不会新增节点。 某个字符串是另一字符串的前缀时, 对应节点也只会建立一次。 节点总是晚于父节点建立。 所以,无需保存儿子表。 按编号倒序枚举节点, 将 $f_i+1$ 异或到父节点即可。 设全部字符串的总长度为 $S$。 建树和计算 SG 值需要 $O(S)$ 时间。 排序复杂度为 $O(S\log N)$, 总空间复杂度为 $O(S)$。 ## 参考代码 ```cpp #include <bits/stdc++.h> using namespace std; const int N=1000005; int fa[N],f[N],path[N]; int main() { ios::sync_with_stdio(false); cin.tie(nullptr); int t; cin>>t; while(t--) { int n; cin>>n; vector<string> a(n); for(auto &s:a)cin>>s; sort(a.begin(),a.end()); int cnt=0; f[0]=path[0]=0; for(int i=0;i<n;i++) { int len=a[i].size(),p=0; if(i) { int last=a[i-1].size(); int m=min(len,last); while(p<m&&a[i][p]==a[i-1][p])p++; } for(int j=p;j<len;j++) { cnt++; fa[cnt]=path[j]; f[cnt]=0; path[j+1]=cnt; } } for(int i=cnt;i;i--)f[fa[i]]^=f[i]+1; cout<<(f[0]?"XiaoLan":"XiaoQiao")<<'\n'; } return 0; } ```