字符串游戏's 题解

来自信息学奥赛一本通T1819
题目描述:
游戏分为 k k k 轮,参与者共有2个。
给定一个由小写英文字母组成的字符串的集合 S S S,在每轮游戏开始时,会有一个空串,然后两人轮流在该串的末尾添加字符,并且需要保证新的字符串是 S S S 中某个串的前缀,直到有一方不能操作,则不能操作的一方输掉这一轮。
新的一轮由上一轮输的人先手,最后一轮赢的人获得游戏胜利。
假定双方都采取最优策略,求第一轮先手的一方能否获胜。
本以为自己逃脱了字符串的魔咒,结果你在博弈论最后一题来了个tire树???
那既然是tire树那么就交给要讲tire树的dalao来做吧。(雾)
啊,这道题非常的容易想到构造,直接建出一棵tire树,在树上跑一遍dfs看先手是否有必胜或必败策略再判断一下就可以了,如果当前点 x x x 的任意一个某个子节点 y y y 没有必胜策略,那么 x x x 就有必胜策略,同理必败策略也是如此,那就可以用一个有两个位的二进制数来表示,前一位表示是否有必胜策略,后一位表示是否有比败策略,转移的时候就是当前的答案或上所有儿子节点的答案取反(这里应改用异或3),还有一点就是显然叶子结点(没有出边的点)只有先手必败状态,那么就可以开始转移了。
再考虑一下不同策略的影响:
(1)先手既没必胜策略也没必败策略:那么这种状况是接下来不管走到哪对方都可以控制必胜或必败,接下来跳转到(4)一起证明;
(2)先手只有必败策略:非常显然,后手可以一直让先手输,使得先手下一句还是先手,一直输到底;
(3)先手只有必胜策略:那么显然这就和进行的局数有关了,如果为奇数则先手胜,偶数则后生胜;
(4)先手既有必胜策略又有必败策略:那么先手可以在前 k − 1 k-1 k1 轮保证自己输,使得自己可以在第 2 2 2 k k k 次保证自己的先手地位,再在第 k k k 次下出必胜策略赢得比赛。
时间复杂度 O ( n ) O(n) O(n)
在临近结尾在吐个槽,数据范围貌似没说清楚应为所有字符串长度加起来小于1e5,否则输入就直接超时了。然后这个代码我调了一个下午,因为小数据一直超时,搞得我整个人都不耐烦了。后来还看了书上的题解,发现思路没有问题,再理解了下它丑的要死 的代码,发现根本和我一样没有一点优化,那为什么会超时呢。。。再想了30分钟左右的光景,我随手加了一个O2它就过了。。。我非常的无语,为啥这oj还能自己开O2开关(平常用习惯了不能开O2的oj的某只蒟蒻如是说到)???这个故事告诉我们,如果无法优化时间的代码不妨手动开个O2试试(除非已知这oj无法开O2,比如HLOJ)?
code:

#include <bits/stdc++.h>
#pragma GCC optimize(2)
using namespace std;
const int N=1e5+10;
int n,k,t,num,sum;
char s[N];
struct lol {
	int trs[26];
	int cln(){
		for(int i=-1;++i<26;trs[i]=0);
	}
} e[N*26];
int init(){
	if(scanf("%d%d",&n,&k)==EOF) return 0;
	return 1;
}
void inst(){//将新的字符串插入trie树
	for(int i=-1,ans=1,now;++i<strlen(s);){
		now=s[i]-'a';
		e[ans].trs[now]?:e[e[ans].trs[now]=++num].cln();
		ans=e[ans].trs[now];
	}
}
int dfs(int x){//判断从当前节点x开始添加字符,是否有先手必胜或必败的策略
	int cnt=0,fl=1;//fl表示是否有出边 
	for(int i=-1,y;++i<26;y=e[x].trs[i],!y?:(cnt|=dfs(y)^3,fl=0));
	//如果有边则搜索下去,因为这时先后手换了,那么就要把答案反过来(异或3)
	return fl?1:cnt;//如果这个点没有出边了,那么显然是先手必败的状态了 
}
void work(){
	e[num=1].cln();
	for(int i=0;++i<=n;scanf("%s",s),inst());
	sum=dfs(1);
	//sum二进制有两位,前一位表示是否有必胜策略,后一位表示是否有必败策略
}
void prin(){
	sum/2?
		sum%2?
			printf("HY wins!\n"):
			printf(k&1?"HY wins!\n":"Teacher wins!\n"):
		printf("Teacher wins!\n");
}
int main(){
	for(;init();work(),prin());
	return 0;
} 

博弈论5道over,写拍拍他去了。

感谢各位dalao捧场
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值