做完CTSC的题目感觉整个人都不好了。。。
首先将母串中间插入'2'然后连成一串建立后缀自动机。
对于每一个询问:显然可以发现如果串s对于L如果是“熟悉的文章”,那么任意L'<=L必然是“熟悉的”,于是二分答案x,令f[i]表示到第i位的可以得到的熟悉的子串长度,可以得到方程:
f[i]=max{f[j]+i-j},其中i-j>=L且s[i..j]在母串中出现。
如果利用后缀自动机,我们可以均摊O(1)得到以i为右端点向左延伸的最远的距离,即j的下界;另外由i-j>=L→j<=i-L,即j的上界。另一方面,如果将f[j]-j作为一个整体,就可以用单调队列维护,于是可以均摊O(1)得到f[i]的值。
最后判断f[i]/len(s[j])是否不小于0.9即可。
AC代码如下(写得好慢参考即可。如果把求j的下界作为一个独立的函数去求就会快得多了(不要问我为什么)):
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 2250005
using namespace std;
int n,m,tot,last,len,ch[N][3],mx[N],fa[N],f[N],q[N]; char s[N];
void extend(int x){
int p=last,np=last=++tot; mx[np]=mx[p]+1;
for (; p && !ch[p][x]; p=fa[p]) ch[p][x]=np;
if (!p) fa[np]=1; else{
int q=ch[p][x];
if (mx[p]+1==mx[q]) fa[np]=q; else{
int nq=++tot; mx[nq]=mx[p]+1; fa[nq]=fa[q];
memcpy(ch[nq],ch[q],sizeof(ch[q]));
for (fa[np]=fa[q]=nq; ch[p][x]==q; p=fa[p]) ch[p][x]=nq;
}
}
}
bool ok(int x){
int i,head=1,tail=0,now=1,tmp=0;
for (i=1; i<=len; i++){
int c=s[i]-'0',j=i-x; f[i]=f[i-1];
while(now && !ch[now][c]) now=fa[now];
tmp=min(tmp,mx[now])+1; now=(now)?ch[now][c]:1;
if (j>=0){ while (head<=tail && f[j]-j>=f[q[tail]]-q[tail]) tail--; q[++tail]=j; }
while (head<=tail && q[head]<i-tmp) head++;
if (head<=tail) f[i]=max(f[i],f[q[head]]+i-q[head]);
}
return f[len]*10>=len*9;
}
int main(){
scanf("%d%d",&n,&m); int i,j; tot=last=1;
for (i=1; i<=m; i++){
scanf("%s",s); for (j=0; s[j]; j++) extend(s[j]-'0'); extend(2);
}
for (i=1; i<=n; i++){
scanf("%s",s+1); len=strlen(s+1); int l=0,r=len;
while (l+1<r){
int mid=(l+r)>>1; if (ok(mid)) l=mid; else r=mid-1;
}
if (ok(r)) printf("%d\n",r); else printf("%d\n",l);
}
return 0;
}
by lych
2016.2.7