分析:
一看到这种最大值的问题,冥冥之中可以感知到是二分在召唤我们
我们把所有的标准串扔到
SAM
S
A
M
里,建出一个广义后缀自动机
二分一个
L
L
,用dp判断可行性
怎么用dp呢dp呢dp呢
其实这个问题,就是求把文章分段后的最大的匹配长度
我们先把模式串放到上跑一遍
得到
len
l
e
n
数组,表示
i
i
位置之前最大公共长度
得到这个有什么用呢?
设计DP状态:表示前
i
i
个字符最长熟悉长度
对于当前我们考虑的状态,我们只要考虑它为某一段尾部最后一个字符就可以了
首先,如果当前位置的撑破天都达不到,我们就没有必要转移这个位置
其次, f[i]=f[i−1] f [ i ] = f [ i − 1 ] ,因为第 i i 位的匹配长度肯定不会小于前一个位置的匹配长度
再其次,当前状态的决策区间(上一个区间的结尾)只可能是
因为小于
i−len
i
−
l
e
n
的位置是不能匹配的
那么我们很容易就可以得到转移:
f[i]=max(f[i−1],f[j]+i−j|i−len[i]<=j<=i−L)
f
[
i
]
=
m
a
x
(
f
[
i
−
1
]
,
f
[
j
]
+
i
−
j
|
i
−
l
e
n
[
i
]
<=
j
<=
i
−
L
)
其中
[j+1,i]
[
j
+
1
,
i
]
就是这一段的匹配长度
但是这样是 O(n2) O ( n 2 ) 的复杂度,我们能不能优化呢
因为 i−len[i] i − l e n [ i ] 严格单调不减,也就是说转移点单调不减,所以我们可以用单调队列优化
双端队列
- 队尾:
强调一点:队列中的元素一定是能够转移此结点的状态
我们之前已经说过了, i i 结点的转移区间只有
每当 i i 在向后移动的时候,唯一可能产生的新的转移点就是
那我们就在队尾插入这个转移点: (i−L) ( i − L )
观察一下转移方程: f[i]=f[j]+i−j f [ i ] = f [ j ] + i − j
由转移点决定的值只有: f[j]−j f [ j ] − j
所以我们直接对比这个值即可,保证队列中单调不增
while (tou<=wei&&f[q[wei]]-q[wei]<f[i-x]-(i-x)) wei--;
- 队首:判断队首的点是否在决策区间内
因为队列是单调不增的,所以队首状态就一定是最优的了: f[i]=max(f[i],f[q.front]+i−q.front) f [ i ] = m a x ( f [ i ] , f [ q . f r o n t ] + i − q . f r o n t )
最后,判断是否 f[n] f [ n ] 是否满足90%的条件即可
tip
注意 f[0] f [ 0 ] 也是一个转移状态
insert的时候++sz写错了。。。mmp
#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;
const int N=1100003;
int n,m,ch[N<<1][2],dis[N<<1],fa[N<<1],last=1,root=1,sz=1,len;
int L[N],f[N],q[N<<1];
char s[N];
void insert(int x)
{
int now=++sz,pre=last;
last=now;
dis[now]=dis[pre]+1;
for (;pre&&!ch[pre][x];pre=fa[pre]) ch[pre][x]=now;
if (!pre) fa[now]=root;
else {
int q=ch[pre][x];
if (dis[q]==dis[pre]+1) fa[now]=q;
else
{
int nows=++sz;
dis[nows]=dis[pre]+1;
memcpy(ch[nows],ch[q],sizeof(ch[q]));
fa[nows]=fa[q]; fa[q]=fa[now]=nows;
for (;pre&&ch[pre][x]==q;pre=fa[pre]) ch[pre][x]=nows;
}
}
}
void get()
{
int now=root,sum=0;
for (int i=1;i<=len;i++) //在SAM上匹配
{
int x=s[i]-'0';
if (ch[now][x]!=0)
now=ch[now][x],sum++;
else
{
while (now&&!ch[now][x]) now=fa[now];
if (!now) now=root,sum=0;
else sum=dis[now]+1,now=ch[now][x];
}
L[i]=sum;
}
}
bool check(int x)
{
int tou=1,wei=0;
for (int i=1;i<=len;i++)
{
f[i]=f[i-1];
if (i-x<0) continue; //此状态没有转移意义
while (tou<=wei&&f[q[wei]]-q[wei]<f[i-x]-i+x) wei--;
q[++wei]=i-x;
while (tou<=wei&&q[tou]<i-L[i]) tou++; //决策区间
if (tou<=wei) f[i]=max(f[i],f[q[tou]]+i-q[tou]);
}
return f[len]*10>=len*9;
}
int solve()
{
get();
int l=0,r=len,ans=0;
while (l<=r)
{
int mid=(l+r)>>1;
if (check(mid)) ans=max(ans,mid),l=mid+1;
else r=mid-1;
}
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++)
{
scanf("%s",s+1);
last=root; len=strlen(s+1);
for (int j=1;j<=len;j++) insert(s[j]-'0');
}
for (int i=1;i<=n;i++)
{
scanf("%s",s+1);
len=strlen(s+1);
printf("%d\n",solve());
}
return 0;
}