做完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