【男人八题】 A.String Game(后缀自动机 + sg函数)

17 篇文章 0 订阅
8 篇文章 0 订阅

题目:https://nanti.jisuanke.com/t/24852

题目大意:给一个模式串和n个它的子串,Alice和Bob玩游戏,Alice先手,每回合任选一个子串,该回合轮到的人在它后面加一个字母,并且保证加了之后的新串仍然是模式串的子串。轮到后没办法保证上述添加要求的人输。

(虽然题目没有说,但是字符集是小写字母)

可以看出这其实是一个经典的n堆nim博弈,可以用每个子串的sg函数异或值来求。

问题就在于求sg函数需要枚举所有走法,然后再取不与走了之后状态sg值重复的最小非负。

Sam很好的提供了所有走法,只要能在自动机里走,就代表生成的新串仍然是模式串的子串。

并且因为他们采取最优走法,所以sam的终点(last)一定是每个给定子串最终的归宿(最终游戏局面)。

即sg[last] = 0。

那么就可以将自动机排序后从后往前求每个节点的sg,然后把给定子串走到对应状态,取出其sg值,全部异或后即是要求的最终sg。

这样做其实把这个游戏转换成了取石子,加字符实际上是从剩下可走的状态集(石子堆)中取字符。

jojo!我不做女装大佬啦,我在男人八题签到啦!!!!!!!

感谢sg-master权哥帮我复习sg函数。

ac代码:

#include<bits/stdc++.h>
using namespace std;

const int maxn = 100005;
int n;
char s[maxn];
char t[105][maxn];

struct Sam {
	int next[maxn << 1][26];
	int link[maxn << 1], step[maxn << 1];
	int sg[maxn << 1], vis[30];
	int a[maxn], b[maxn << 1];
	int sz, last, len, root;

	void init() {
		for(int i = 0; i <= sz; i++) {
			memset(next[i], 0, sizeof(next[i]));
		}
		memset(a, 0, sizeof(a));
		root = sz = last = 1;
	}

	void add(int c) {
		int p = last;
		int np = ++sz;
		last = np;

		step[np] = step[p] + 1;
		while(!next[p][c] && p) {
			next[p][c] = np;
			p = link[p];
		}

		if(p == 0) {
			link[np] = root;
		} else {
			int q = next[p][c];
			if(step[p] + 1 == step[q]) {
				link[np] = q;
			} else {
				int nq = ++sz;
				memcpy(next[nq], next[q], sizeof(next[q]));
				step[nq] = step[p] + 1;
				link[nq] = link[q];
				link[q] = link[np] = nq;
				while(next[p][c] == q && p) {
					next[p][c] = nq;
					p = link[p];
				}
			}
		}
	}

	void build() {
		init();
		for(int i = 0; s[i] ; i++) {
			add(s[i] - 'a');
		}
		for(int i = 1; i <= sz; i++) {
			a[step[i]]++;
		}
		for(int i = 1; i <= step[last]; i++) {
			a[i] += a[i - 1];
		}
		for(int i = 1; i <= sz; i++) {
			b[a[step[i]]--] = i;
		}
	}

	void grundy() {
		sg[last] = 0;
		for(int i = sz; i > 1; i--) {
			int e = b[i];
			int g = 0;
			memset(vis, 0, sizeof(vis));
			for(int j = 0; j < 26; j++) {
				if(!next[e][j]) {
					continue;
				}
				vis[sg[next[e][j]]] = 1;
			}
			while(vis[g]) {
				++g;
			}
			sg[e] = g;
		}
	}

	void solve() {
		build();
		grundy();
		int sum = 0;
		for(int i = 1; i <= n; i++) {
			int p = root;
			for(int j = 0; t[i][j] ; j++) {
				p = next[p][t[i][j] - 'a'];
			}
			sum ^= sg[p];
		}
		printf("%s\n", sum ? "Alice" : "Bob");
	}

} sam;

int main() {
	while(~scanf("%s", s)) {
		scanf("%d", &n);
		for(int i = 1; i <= n; i++) {
			scanf("%s", t[i]);
		}
		sam.solve();
	}
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值