Codeforces Round 260 (Div. 1) B. A Lot of Games(字典树+博弈)

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

思路

分情况讨论:

  1. 有先手必胜策略,且有先手必败策略
    那么前 k − 1 k - 1 k1 轮我们都使用必败策略,保证下一次是我们先手,第 k k k 次时使用必胜策略,A必胜。

  2. 有先手必胜策略,没有先手必败策略
    假设每次都是先手胜利,那么最终结果就是确定的,最终失败的那一方若想改变结果则必须在若干局形成先手失败局面,但不存在必败的策略,胜利那一方不会允许出现这样的局面。
    那么每次都是先手必胜,两个人轮转胜利 当 k k k 是奇数时A胜利,否则B胜利。

  3. 没有先手必胜策略
    那么后手必然每次都会取胜,使得先后手的人不变,B必胜。

这里的没有先手必败策略可以理解为,后手有一种策略使得自己必败

用字典树存储字符串,将每个字符串的每个字符看做不同的节点,相同前缀的可以互相转移,建图以后就相当于朴素的单一起点的有向图博弈了。

代码

#include 
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;
}

你可能感兴趣的:(博弈论,深度优先,图论,算法)