B. A Lot of Games
链接:Codeforces Round 260 (Div. 1) B. A Lot of Games
题意
给定 n n n 个字符串,A和B准备玩一个游戏,每一轮有一个初始空字符串,每人轮流向其中添加字符,要求添加后的字符串必须是这 n n n 个字符串其中一个的前缀,当有人不能行动时这个人就输了。现在进行 k k k 轮游戏,每一轮的先手为上一轮的败者,第一轮A先手,问第 k k k 轮胜利的人是谁。
n < = 1 0 5 , k < = 1 0 9 n<= 10^5,k<=10^9 n<=105,k<=109,字符串长度之和 l e n < = 1 0 5 len<=10^5 len<=105。
思路
分情况讨论:
-
有先手必胜策略,且有先手必败策略
那么前 k − 1 k - 1 k−1 轮我们都使用必败策略,保证下一次是我们先手,第 k k k 次时使用必胜策略,A必胜。 -
有先手必胜策略,没有先手必败策略
假设每次都是先手胜利,那么最终结果就是确定的,最终失败的那一方若想改变结果则必须在若干局形成先手失败局面,但不存在必败的策略,胜利那一方不会允许出现这样的局面。
那么每次都是先手必胜,两个人轮转胜利 当 k k k 是奇数时A胜利,否则B胜利。 -
没有先手必胜策略
那么后手必然每次都会取胜,使得先后手的人不变,B必胜。
这里的没有先手必败策略可以理解为,后手有一种策略使得自己必败
用字典树存储字符串,将每个字符串的每个字符看做不同的节点,相同前缀的可以互相转移,建图以后就相当于朴素的单一起点的有向图博弈了。
代码
#include <bits/stdc++.h>
using namespace std;const int N = 1e5 + 10;
int son[N][26], tot;
vector<int> g[N], e[N];string s[N];int siz[N], pre[N];
void insert(int id){int p = 0, q = 1;for(auto ch : s[id]){int u = ch - 'a';if(!son[p][u]) son[p][u] = ++ tot;p = son[p][u];g[p].push_back(pre[id - 1] + (q ++)); // 将前缀相同的节点存在一起}
}void build(int p){ // 建图if(!p) return ;for(auto u : g[p]){for(int j = 0; j < 26; j ++){for(auto v : g[son[p][j]]){e[u].push_back(v);}}}for(int j = 0; j < 26; j ++) build(son[p][j]);
}int f[N][2]; // f[i][0]代表追求胜利 = 0代表先手必败,= 1 代表先手必胜, f[i][1] 代表追求胜利 = 0代表先手胜利,= 1 代表先手必败
int dfs(int u, int op){if(f[u][op] != -1) return f[u][op];f[u][op] = !op;for(auto v : e[u]){f[u][op] |= dfs(v, op);if(f[u][op]) return !f[u][op];}return !f[u][op];
}int main(){ios::sync_with_stdio(false);cin.tie(0); cout.tie(0);int n, k;cin >> n >> k;for(int i = 1; i <= n; i ++) cin >> s[i];for(int i = 1; i <= n; i ++){siz[i] = s[i].size();pre[i] = pre[i - 1] + siz[i];insert(i);}for(int i = 0; i < 26; i ++) build(son[0][i]);memset(f, -1, sizeof f);int f1 = 0, f2 = 0;//f1:存在先手必败策略,f2:存在先手必胜策略for(int i = 1; i <= n; i ++){f1 |= dfs(pre[i - 1] + 1, 0);f2 |= dfs(pre[i - 1] + 1, 1);}if(f1 && f2) cout << "First\n";else if(!f1 && f2){if(k & 1) cout << "First\n";else cout << "Second\n";}else cout << "Second\n";return 0;
}