题目大意:
给定一些标准串。
给定一个串,要求一个L使得在这个序列中可以取出若干个长度超过L的子串(这些子串必须是给定标准串的子串),且取出的串的总长度超过整个串的90%。
求最大的L。
题目分析:
先用后缀自动机求出以每个位置为结尾往前最多能匹配多少位,我们用数组a[i]来存储这个值。
这个只要对于所有的标准串建一个广义的后缀自动机,然后把这个串放进去跑一遍,如果能匹配的话,这一位就比上一位多匹配一位,否则跳parent,并且将当前匹配上的最大长度设为当前节点的max_len再继续匹配(详见代码。
然后我们要求L最小值最大,这种问题可以想到二分。
问题转化成了给定L,求是否能取出大于等于90%的子串使之满足条件。
这样我们设f[i]表示匹配到第i个位置匹配不上的字符有多少个。
那么转移很显然就是Min{f[i-1]+1,f[(i-a[i])~f[i-L]]};
这样的话我们很显然有一个时间复杂度上界是n^2的判定方法。
这样的时间复杂度不能接受,但是我们可以发现,i-a[i]是单调递增的,i-L也是单调递增的,那么我们可以用一个单调队列来维护这个这个这个值。
这样时间复杂度就降到了O(n)
算上二分,O(nlog(n))
代码如下:
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 1200000
using namespace std;
int n,m;
int len;
char s[N];
int f[N],a[N],dl[N];
struct SAM{
SAM *son[2],*fa;
int max_len;
SAM(int _):max_len(_)
{
memset(son,0,sizeof(son));
fa=NULL;
}
}*root=new SAM(0),*last=root;
void extend(int x)
{
SAM *p=last;
SAM *np=new SAM(p->max_len+1);
while(p && !p->son[x]) p->son[x]=np,p=p->fa;
if(!p) np->fa=root;
else
{
SAM *q=p->son[x];
if(p->max_len+1==q->max_len) np->fa=q;
else
{
SAM *nq=new SAM(p->max_len+1);
nq->fa=q->fa;
memcpy(nq->son,q->son,sizeof(nq->son));
q->fa=nq; np->fa=nq;
for(;p && p->son[x]==q;p=p->fa) p->son[x]=nq;
}
}
last=np;
}
bool check(int L)
{
int l=1,r=0;
f[0]=0;
for(int i=1;i<=len;i++)
{
f[i]=f[i-1]+1;
if(i-L>=0)
{
while(r>=l && f[i-L]<=f[dl[r]]) r--;
dl[++r]=i-L;
}
while(r>=l && dl[l]<i-a[i]) l++;
if(r>=l) f[i]=min(f[i],f[dl[l]]);
}
return f[len]*10<=len;
}
int main()
{
scanf("%d%d",&n,&m);
while(m--)
{
last=root;
scanf("%s",s);
for(int i=0;s[i];i++)
extend(s[i]-'0');
}
while(n--)
{
scanf("%s",s+1);
len=strlen(s+1);
int now=0,x;
SAM *c=root;
for(int i=1;i<=len;i++)
{
x=s[i]-'0';
while(c!=root && !c->son[x])
{
now=c->fa->max_len;
c=c->fa;
}
if(c->son[x])
{
c=c->son[x];
now++;
}
a[i]=now;
}
int l=0,r=len,ans=0;
while(l<=r)
{
int mid=l+r>>1;
if(check(mid)) ans=mid,l=mid+1;
else r=mid-1;
}
printf("%d\n",ans);
}
return 0;
}