题目描述
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;
}
}
}