cf455 B. A Lot of Games(字典树上博弈)


题意:

给定n个字符串。初始有个空串,两人轮流往串末尾每次加一个字符,要求任意时刻得到的字符串都是某个给定字符串的前缀。不能操作的人输。

现重复进行k次这个游戏,每次由上一次输的人先手,第k次游戏的胜者为最终胜者。问谁能赢

所有串长之和 \(\le 1e5\)

思路:

先只考虑单独的一次游戏。实际上是在一棵字典树上从根开始向下移动(定义一个虚根0),不能移动者输。

叶子节点没有儿子,为必败节点(记为 ansi=0)。然后从下往上递推每个节点的状态:

若某节点的儿子全是必胜点,则该节点为必败点;若某节点的儿子全是必败点,则该节点为必胜点(记为1);

若某节点的儿子中既有必胜点又有必败点,则该节点为可控点(记为2)

(注意可控是最好的状态,应尽量使点可控;被控是最差的状态,应尽量避免)

若某节点的儿子全是可控点,则该节点为被控点(记为3);若某节点的儿子中有一个被控点,则该节点为可控点;

若某节点的儿子全是必败和可控,则该节点必胜;若某节点的儿子全是必胜和可控,则该节点必败。

经过上面的递推可以算出 ans[0],即单次游戏的状态。若单次游戏先手必败,则k次游戏先手必败;若单次游戏先手必胜,则k次游戏的胜负取决于k的奇偶性;

若单次游戏先手可控,则先手者可以让自己每次都先手,故先手必胜;若单次游戏先手被控则先手必败。

const int N = 1e5 + 5;
int n, k; string s;

int son[N][26], idx;
void add(string s)
{
    int p = 0;
    for(char x : s)
    {
        int u = x - 'a';
        if(!son[p][u]) son[p][u] = ++idx;
        p = son[p][u];
    }
}

int ans[N];
void dfs(int u)
{
    ans[u] = 0;
    bool have[4] = {0,0,0,0}; //儿子有什么
    for(int i = 0; i < 26; i++)
    {
        int v = son[u][i];
        if(v) dfs(v), have[ans[v]] = 1;
    }
    if(have[3]) ans[u] = 2; //儿子有被控
    else if(have[0] && have[1]) ans[u] = 2; //有胜有败
    else if(have[0]) ans[u] = 1;
    else if(have[1]) ans[u] = 0;
    else if(have[2]) ans[u] = 3;
}

signed main()
{
    cin >> n >> k;
    while(n--) cin >> s, add(s);

    dfs(0);

    if(ans[0] == 2) puts("First");
    if(ans[0] == 3) puts("Second");
    if(ans[0] == 1) puts(k&1 ? "First" : "Second");
    if(ans[0] == 0) puts("Second");
}