jzoj1895. 单词争霸

题目描述

Description
农夫约翰(Farmer John)的奶牛们平时喜好一起学习英文单词。奶牛Bessie是其中最聪明的牛,她发明了一个游戏,叫做《单词争霸》。

这个游戏是由两个人来玩,两人轮流进行。轮到每个人时,他要说出一个正确的单词(即字典中的单词),要求这个单词在之前没有被他或他的对手说过,并且这个单词不是之前他或他的对手说过的某个单词的前缀。如果轮到一个人,他无法说出这样的单词,那么他被判败。

显然,拥有词汇量较多的奶牛玩起这个游戏来更有优势,因为他有更多的单词可以说。这样,奶牛们天天玩这个游戏,他们的词汇量越来越大……

直到有一天,Bessie发现他们已经把世界上所有的英文单词学会了,因此她再也无法依赖自己较大的词汇量取胜了。她是记忆单词的天才,但并不是游戏好手,所以她来请教聪明的你。

她告诉你,这个世界上一共有N个英文单词,每个单词的长度不超过maxLen。她将这些单词按字典序排列,并输入。她想知道如果她是先手,那么她是否能取得胜利。

你需要做的是,判断在给定字典的情况下,先手是否有必胜策略。如果没有,那么告诉Bessie:“Can’t win at all!!”,否则,你需要确定先手在第一回合可以说出哪些单词,为了让Bessie多一些思考的乐趣,你决定不把这些单词一一列举。你把这些单词按某个排列连接起来,组成一个大的字符串,当然,不同的排列可能得到不同的串,你要告诉Bessie那个字典序最先的串,记作answerString。

为了使输出更加美观,如果那个串的长度大于50,你要分行输出,除了最后一行以外,其他行每行50个字符,最后一行不多于50个字符。

Input
输入的第一行包含两个整数,N和maxLen,用一个空格隔开。

接下来的N行,每行为一个长度不大于maxLen的字符串。

Output
按题目描述中的要求输出:

可能为一行,“Can’t win at all!!”(不包含引号)   或者多行,除了最后一行之外每行50个字符,最后一行不超过50个字符,将所有行的字符连接起来是answerString。

Sample Input
输入1:

2 9

word

wordcraft

输入2:

5 9

ac

car

care

careful

carefully

输入3:

2 100

noonecansolvethisproblem

thisproblemisverydifficult

输入4:

9 8

theworda

thewordb

thewordc

thewordd

theworde

thewordf

thewordg

thewordh

thewordi

Sample Output
输出1:

wordcraft

样例1解释:

先手说出单词“wordcraft”之后,后手没有单词可说,因为单词“word”是单词“wordcraft”的子串。

输出2:

careful

样例2解释:

先手说“careful”之后,后手只能说“ac”或者“carefully”。如果他说了前者,那么先手说后者;反之,如果他说了后者,那先手说前者。之后,后手都将无单词可说。

输出3:

Can’t win at all!!

输出4:

thewordathewordbthewordctheworddthewordethewordfth

ewordgthewordhthewordi

Data Constraint
输入数据中的所有单词均由小写字母构成。

输入数据保证所有单词按字典序排列。

输入数据保证所有单词互不相同。

10%的数据中N≤20,maxLen≤20;

30%的数据中N≤500,maxLen≤50;

50%的数据中N≤2000,maxLen≤50;

70%的数据中N≤5000,maxLen≤100;

​100%的数据中N≤100000,maxLen≤100。

Hint
注意:不保证输入数据只有字母

转化

先求出串与串之间的前缀关系
显然trie会挂而且里面还有数字

假设要求fa[i],已知fa[1~i-1]
如果i-1是i的前缀,那么fa[i]=i-1
否则,fa[i]一定是i-1和i的前缀,也是i-1、i最长公共前缀的前缀
所以找出公共前缀后从fa[i-1]往前跳,直到跳到的串长度不大于公共前缀长度就是fa[i]
自行画图理解

SG

考虑计算每棵子树的SG值
显然叶子的SG=1
但这题不同于经典的树上移石子问题(每次只能向下移一步),经典问题移动后还是一棵树,但本题删掉链会把树拆成森林,即一个局面(若干状态的SG的异或和)

正解

辣鸡题解
考虑以每个点为根的子树,然后暴力删链同时计算SG的异或和
找答案就找删去某条链(不能只删根节点)后剩余SG异或和为0的
不可能有两个答案串AB有前缀关系
证明:
如果A是B的前缀,且先删A和先删B都可以赢
那么如果先删了A,对手可以删B,这样对手就赢了,与条件矛盾

时间复杂度证明:
总时间=每棵子树大小=每个点到根的距离=O(n*maxlen)
没了

另一种解法

来自zjq
用trie维护每棵子树中所有删链后的SG异或和
显然向上合并就等于把trie中所有数都异或上自己兄弟节点的SG异或和,在树上打标记
合并类似线段树合并,因为每个点只会加上一次(只删自己),所以时间为O(n log n)
然而这样并没有什么卵用因为读入就已经O(n*maxlen)了

code

正解

#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
using namespace std;

int a[100001][2];
int ls[100001];
int st[100001][101];
int fa[100001];
int len[100001];
int sg[100001];
bool bz[131072];
int ans[100001];
int n,mlen,i,j,k,l,Len,tot;
char ch;

void New(int x,int y)
{
	++Len;
	a[Len][0]=y;
	a[Len][1]=ls[x];
	ls[x]=Len;
}

int qz(int x,int y)
{
	int i=0;
	
	while (i<len[x] && i<len[y] && st[x][i+1]==st[y][i+1])
	++i;
	
	return i;
}

void Dfs(int t,int s)
{
	int i;
	
	for (i=ls[t]; i; i=a[i][1])
	s^=sg[a[i][0]];
	
	bz[s]=1;
	
	for (i=ls[t]; i; i=a[i][1])
	Dfs(a[i][0],s^sg[a[i][0]]);
}

void Dfs_del(int t,int s)
{
	int i;
	
	for (i=ls[t]; i; i=a[i][1])
	s^=sg[a[i][0]];
	
	bz[s]=0;
	
	for (i=ls[t]; i; i=a[i][1])
	Dfs_del(a[i][0],s^sg[a[i][0]]);
}

void dfs(int t)
{
	int i;
	
	if (!ls[t])
	{
		sg[t]=1;
		return;
	}
	
	for (i=ls[t]; i; i=a[i][1])
	dfs(a[i][0]);
	
	Dfs(t,0);
	
	i=0;
	while (bz[i])
	++i;
	sg[t]=i;
	
	Dfs_del(t,0);
}

void DFS(int t,int s)
{
	int i;
	
	for (i=ls[t]; i; i=a[i][1])
	s^=sg[a[i][0]];
	
	if (t && !s)
	ans[++tot]=t;
	
	for (i=ls[t]; i; i=a[i][1])
	DFS(a[i][0],s^sg[a[i][0]]);
}

int main()
{
//	freopen("S8_1_2.in","r",stdin);
	
	scanf("%d%d\n",&n,&mlen);
	fo(i,1,n)
	{
		ch=getchar();
		while (ch!='\n')
		{
			st[i][++len[i]]=ch-'a';
			ch=getchar();
		}
	}
	
	fo(i,2,n)
	{
		l=qz(i-1,i);
		for (j=i-1; len[j]>l; j=fa[j]);
		
		fa[i]=j;
	}
	
	fo(i,1,n)
	New(fa[i],i);
	
	dfs(0);
	DFS(0,0);
	
	if (!tot)
	printf("Can't win at all!!\n");
	else
	{
		sort(ans+1,ans+tot+1);
		
		i=1;
		j=1;
		l=0;
		while (i<=tot)
		{
			if (l==50)
			{
				printf("\n");
				l=0;
			}
			
			printf("%c",char(st[ans[i]][j]+'a'));
			
			++j;
			if (j>len[ans[i]])
			{
				++i;
				j=1;
			}
			++l;
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值