XJOI模拟训练11 - C.Game
C.Game
题目描述 :
给出一个由小写英文字母构成的字符串 S,再取 n 个字符串,它们都是 S 的子串,之后开始游戏,两人轮流操作: 每次选一个串,在其后添加一个英文字母,保证添加后该串仍为 S 的子串。 谁不能操作谁输,或者谁不能操作谁胜。 但是春希看出了胜负已定,于是对实际操作没有兴趣,所以 Pickupwin 只要求他指明先手是否必胜就好。
输入格式 :
第一行一个字符串 S;
第二行一个正整数 n;
接下来 n 行,每行两个正整数 l,r,表示第 i 个字符串由 S 中第 l 个至第 r 个字符一次拼接而成(S 中字符从 1 开始标号)
输出格式 :
输出 n + n行
第 \(2 * i - 1\) 行输出在仅有前 i 个子串时,谁不能操作谁输,先手是否必胜。
第 \(2 * i\) 行输出在仅有前 i 个子串时,谁不能操作谁胜,先手是否必胜。
用 Fir 表示先手必胜, Sec 表示后手必胜。
样例 :
输入 :
sroababorz
3
4 5
7 8
1 3
输出 :
Fir
Sec
Fir
Sec
Sec
Fir
数据范围 :
50% |S| <= 200,n <= 100
100% |S| <=\(10^6\), n <= \(5*10^5\)
Solution :
Anti-Nim游戏 :
定义:
桌子上有 N 堆石子,游戏者轮流取石子。
每次只能从一堆中取出任意数目的石子,但不能不取。
取走最后一个石子者败。
结论:
先手必胜当且仅当:
2. 所有堆的石子数都为 1 且游戏的 SG 值为 0;
3. 有些堆(>=1)的石子数大于 1 且游戏的 SG 值不为 0。
所以我们可以先对字符串 S 建一个后缀自动机,在SAM上跑出每个位置对应的 SG 函数值,对于给出的每个子串,用倍增找到它在 SAM 上对应的位置,得到相应的 SG值,再依据 SG 函数值将它抽象为 Nim游戏,利用Nim和Anti-Nim的结论来判断即可。
Code :
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6 + 7, L = 1e6 + 7;
char s[L];
int n, m, l, r, len;
int id[L], SG[N];
template<class T> void read(T &x) {
bool f = false; x = 0;
char ch = getchar();
while (ch<'0' || ch>'9') {if (ch == '-') f = true; ch = getchar();}
while (ch>='0' && ch<='9') x = x * 10 + ch - '0', ch = getchar();
if (f) x = -x;
}
namespace sam {
int size = 1, prv = 1, cnt = 0;
int maxlen[N], trans[N][27], link[N], buf[N], a[N];
int fa[N][25], fir[N], nxt[N], to[N], d[N];
inline void Extend(int id, int pos) {
int cur = ++size, u;
d[pos] = cur;
// cout<<d[pos]<<" : pos"<<endl;
maxlen[cur] = maxlen[prv] + 1;
for (u = prv; u && !trans[u][id]; u = link[u]) trans[u][id] = cur;
if (u == 0) link[cur] = 1;
else {
int v = trans[u][id];
if (maxlen[v] == maxlen[u] + 1) link[cur] = v;
else {
int clo = ++size;
maxlen[clo] = maxlen[u] + 1;
memcpy(trans[clo], trans[v], sizeof(trans[v]));
link[clo] = link[v];
for (; u && trans[u][id] == v; u = link[u]) trans[u][id] = clo;
link[cur] = link[v] = clo;
}
}
prv = cur;
}
inline void AddEdge(int u, int v) {
nxt[++cnt] = fir[u];
fir[u] = cnt, to[cnt] = v;
}
inline void dfs(int u) {
fa[u][0] = link[u];
for (int i = 1; i <= 21; i++) {
fa[u][i] = fa[fa[u][i-1]][i-1];
}
for (int i = fir[u]; i; i = nxt[i]) {
int v = to[i];
dfs(v);
}
}
inline int getmex(vector<int> &V) {
sort(V.begin(), V.end());
unique(V.begin(), V.end());
int res = 0;
for (int i = 0; i < (int)V.size(); i++) {
if (V[i] == res) res ++;
else return res;
}
return res;
}
inline void getSG() {
for (int i = 1; i <= size; i++) buf[maxlen[i]]++;
for (int i = 1; i <= size; i++) buf[i] += buf[i - 1];
for (int i = 1; i <= size; i++) a[buf[maxlen[i]]--] = i;
for (int i = size; i >= 1; i--) {
vector<int> A;
for (int j = 0; j < 26; j++) {
if (trans[a[i]][j]) {
A.push_back(SG[trans[a[i]][j]]);
}
}
if (A.empty()) SG[a[i]] = 0; else SG[a[i]] = getmex(A);
}
}
inline int getpos(int l, int r) {
int x = d[r];
// cout<<x<<" : x "<<maxlen[x]<<" :len"<<endl;
for (int i = 21; i >= 0; i--) {
if (maxlen[fa[x][i]] >= r - l + 1) x = fa[x][i];
}
return x;
}
}
using namespace sam;
int main() {
scanf("%s", s + 1);
len = strlen(s + 1);
for (int i = 1; i <= len; i++) {
Extend(s[i] - 'a', i);
}
for (int i = 2; i <= size; i++) AddEdge(link[i], i);
dfs(1); getSG();
read(n);
for (int i = 1; i <= n; i++) {
read(l), read(r);
id[i] = getpos(l, r);
}
int nw = 0, flag = true;
for (int i = 1; i <= n; i++) {
nw ^= SG[id[i]];
if (nw) puts("Fir");
else puts("Sec");
if (SG[id[i]] > 1) flag = false;
if ((flag && !nw) || (!flag && nw)) puts("Fir");
else puts("Sec");
}
return 0;
}

浙公网安备 33010602011771号