题目链接:http://codeforces.com/contest/727/problem/E
题目大意:有g个长度为k的互不相同的字符串,从中选择n个按照顺时针顺序写下形成一个环s。给出s(长度为n*k)和g个字符串,求是否有一种选择方案可以得到s(可以从任意位置开始)。
数据范围:1 ≤ n ≤ 10^5, 1 ≤ k ≤ 10^5, n*k ≤ 10^6, n ≤ g ≤ 10^5, g*k ≤ 2*10^6
题解:原谅我今天把吐槽放最前面。其实一开始看到这题我只是想写个哈希水一发,结果换了好几个底数死活卡不过第13个点连随机数都不行(话说这个点卡的是自然溢出吧),于是决定换个哈希姿势就把哈希值相同的点存在一个动态数组里,比较的时候偷懒用了string结果还出错了QAQ。然后想了想自己好像还不会后缀自动机就去学了一波hhhh。
1.后缀自动机
设f[ i ]表示s[i , i+k-1]对应第几个字符串,如果不能匹配那就是0。把g个串放进后缀自动机里,用pos[i]记录自动机的第 i 个位置对应可以匹配到哪个字符串。由于蒟蒻对SAM还不是很熟悉所以这里用了个蠢办法:后缀自动机建完后,把每个字符串从根开始跑一发看能跑到哪个点,然后把那个点的pos设为当前字符串的编号,最后再用每个点的父亲的pos更新它。然后从s的第一个位置开始匹配,当i>=k时,用当前匹配到的位置的pos更新f[i-k+1]。
做完这一步后我们从1到k枚举环的起点i,然后判断f[i],f[i+k],f[i+2k]…的值是否都不为0且不重复,如果是那么就是一个合法方案并输出。如果没有找到合法方案就输出NO。
时间复杂度O(g*k+n*k)
(ps:由于写SAM的姿势不对wa了三发,而且这时间(相对来说)有点慢啊hhhh)
代码如下:
#include <algorithm>
#include <cstring>
#include <cstdio>
const int N=1000005;
int ch[N*4][26],fa[N*4],mx[N*4],pos[N*4],f[N*2],u[100005],
num[N*2],d[N*4],tot,root,last,n,k,g;
char a[N*2],b[N*2];
void extend(int c){
int p=last,np=++tot;
mx[np]=mx[p]+1;
for (;p && !ch[p][c];p=fa[p]) ch[p][c]=np;
if (!p) fa[np]=root;
else{
int q=ch[p][c];
if (mx[p]+1==mx[q]) fa[np]=q;
else{
int nq=++tot;mx[nq]=mx[p]+1;
memcpy(ch[nq],ch[q],sizeof ch[q]);
fa[nq]=fa[q];
fa[q]=fa[np]=nq;
for (;p && ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
last=np;
}
int main(){
scanf("%d%d\n%s%d\n",&n,&k,a+1,&g);
int len=n*k;
for (int i=1;i<k;i++) a[len+i]=a[i];
tot=last=root=1;int st=0;
for (int i=1;i<=g;i++){
scanf("%s\n",b+st+1);
for (int j=1;j<=k;j++) extend(b[st+j]-'a');
st+=k;
}
st=0;
for (int i=1;i<=g;i++){
int now=root;
for (int j=1;j<=k;j++) now=ch[now][b[++st]-'a'];
pos[now]=i;
}
for (int i=1;i<=tot;i++) num[mx[i]]++;
for (int i=1;i<=g*k;i++) num[i]+=num[i-1];
for (int i=tot;i;i--) d[num[mx[i]]--]=i;
for (int i=1;i<=tot;i++)
if (!pos[d[i]]) pos[d[i]]=pos[fa[d[i]]];
int now=root,l=0;
for (int i=1;i<len+k;i++){
for (;now && !ch[now][a[i]-'a'];now=fa[now]);
if (!now) now=root,l=-1;
else l=std::min(l,mx[now]),now=ch[now][a[i]-'a'];
if (++l>=k) f[i-k+1]=pos[now];
}
for (int i=1,j;i<=k;i++){
for (j=i;j<=len && f[j] && !u[f[j]];j+=k) u[f[j]]=1;
if (j>len){
printf("YES\n");
for (j=i;j<=len;j+=k) printf("%d ",f[j]);
return 0;
}
for (;j>=i;j-=k) u[f[j]]=0;
}
printf("NO\n");
}
2.AC自动机
事实上这题用AC自动机也可以做而且相比SAM来说更好写也更快。把g个串放进AC自动机里,记下每个串的最后到达位置的pos,在AC自动机上匹配更新f[ i ]就可以了。其余同上。
时间复杂度O(g*k+n*k)
(ps:果然AC自动机才是适合我的正确姿势吗写完一发就A了)
代码如下:
#include <algorithm>
#include <cstring>
#include <cstdio>
const int N=1000005;
int tr[N*2][26],d[N*2],fail[N*2],pos[N*2],f[N*2],
u[100005],tot,n,k,g;
char a[N*2],b[100005];
void add(int id){
scanf("%s",b+1);
int now=0;
for (int i=1;i<=k;i++){
if (!tr[now][b[i]-'a']) tr[now][b[i]-'a']=++tot;
now=tr[now][b[i]-'a'];
}
pos[now]=id;
}
void getfail(){
int s=1,t=0;
for (int i=0;i<26;i++)
if (tr[0][i]) d[++t]=tr[0][i];
for (;s<=t;s++)
for (int i=0;i<26;i++)
if (!tr[d[s]][i]) tr[d[s]][i]=tr[fail[d[s]]][i];
else fail[d[++t]=tr[d[s]][i]]=tr[fail[d[s]]][i];
}
int main(){
scanf("%d%d\n%s%d\n",&n,&k,a+1,&g);
for (int i=1;i<=g;i++) add(i);
getfail();
int len=n*k,now=0;
for (int i=1;i<k;i++) a[len+i]=a[i];
for (int i=1;i<len+k;i++){
now=tr[now][a[i]-'a'];
if (i>=k) f[i-k+1]=pos[now];
}
for (int i=1,j;i<=k;i++){
for (j=i;j<=len && f[j] && !u[f[j]];j+=k) u[f[j]]=1;
if (j>len){
printf("YES\n");
for (j=i;j<=len;j+=k) printf("%d ",f[j]);
return 0;
}
for (;j>=i;j-=k) u[f[j]]=0;
}
printf("NO\n");
}
3.哈希
最后的最后我还是去改了哈希。把g个字符串的哈希值放进map里,相同的用vector存起来,然后每次找s的一段字串是否能匹配某个字符串的时候,只要求出这个子串的hash值x,然后查找map里x的vector大小,如果>=2那么就暴力判断一下有没有相等。剩下的还是同上。
时间复杂度O(g*k+(g+n*k)*log+?)
(ps:问号是因为可能出现多个相同hash值。哈希复杂度虽然有点玄学但是依然是最快的。另:对不起我还是不会用string所以写了暴力判断,一想到wa了10发我就心情复杂hhhh)
代码如下:
#include <iostream>
#include <cstdio>
#include <vector>
#include <map>
using namespace std;
const int p=107;
map<int,vector<int> > hash;
int a[100005],b[100005],f[1100010],n,k,g;
char s[1100005],ss[2000005];
bool check(int x,int y){
for (int i=0;i<k;i++)
if (s[x+i]!=ss[y+i]) return 0;
return 1;
}
int main(){
scanf("%d%d\n%s%d\n",&n,&k,s+1,&g);
int len=n*k;
for (int i=1;i<k;i++) s[len+i]=s[i];
for (int i=1;i<len+k;i++) f[i]=f[i-1]*p+s[i]-'a'+1;
int st=0;
for (int i=1;i<=g;i++){
scanf("%s",ss+st);int h=0;
for (int j=0;j<k;j++) h=h*p+ss[st+j]-'a'+1;
hash[h].push_back(i);st+=k;
}
int pow=1;
for (int i=1;i<=k;i++) pow*=p;
for (int i=1,j;i<=k;i++){
int m=0;
for (j=i;j<=len;j+=k){
int x=f[j+k-1]-pow*f[j-1],y=hash[x].size(),z=0;
if (y==0) break;
if (y==1) z=hash[x][0];
if (y>1){
for (int l=0;l<y;l++)
if (check(j,(hash[x][l]-1)*k)){
z=hash[x][l];break;
}
}
if (!z || b[z]) break;
b[a[++m]=z]=1;
}
if (j>len){
printf("YES\n");
for (j=1;j<=m;j++) printf("%d ",a[j]);
return 0;
}
for (j=1;j<=m;j++) b[a[j]]=0;
}
printf("NO\n");
}