好题啊
(SAM)+单调队列优化(dp)
首先这个(L)满足单调性真是非常显然我们可以直接二分
二分之后套一个(dp)就好了
设(dp[i])表示到达(i)位置熟悉的文章的最大长度
有一个非常显然的(dp)方程
[dp_i=max{dp_j+i-j} (i-j>=mid)
]
同时([j+1,i])这个子串也得是模式串里的一个子串
对于上面那个(dp)方程,我们把(i)提出来,用单调队列维护一下(dp_j-j)的最大值就好了
下面这个限制条件的话,我们可以处理出以(i)这个位置的为结尾的和所有的模式串的最长公共子串的长度(mx[i])
这个处理的话我们直接在(SAM)上过一遍就好了
之后我们就有了第二个限制
[j>=i-mx[i]
]
经过分析我们可以发现(i)每次稳定加(1),而(mx[i])每次最多加(1),所以(i-mx[i])是单调不降的的
于是我们还是可以用单调队列来维护
复杂度(O(Tnlogn))
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define maxn 2000010
#define re register
#define LL long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
char S[maxn];
int fa[maxn<<1],len[maxn<<1],son[maxn<<1][3];
int mx[maxn];
int q[maxn],dp[maxn];
int T,m,n,cnt=1,lst=1;
inline void ins(int c)
{
int f=lst,p=++cnt; lst=p;
len[p]=len[f]+1;
while(f&&!son[f][c]) son[f][c]=p,f=fa[f];
if(!f) {fa[p]=1;return;}
int x=son[f][c];
if(len[f]+1==len[x]) {fa[p]=x;return;}
int y=++cnt;
len[y]=len[f]+1,fa[y]=fa[x],fa[x]=fa[p]=y;
for(re int i=0;i<3;i++) son[y][i]=son[x][i];
while(f&&son[f][c]==x) son[f][c]=y,f=fa[f];
}
inline void query()
{
int now=1,L=0;
for(re int i=1;i<=n;i++)
{
if(son[now][S[i]-'0']) {mx[i]=++L;now=son[now][S[i]-'0'];continue;}
while(now&&!son[now][S[i]-'0']) now=fa[now];
if(!now) {mx[i]=L=0;now=1;continue;}
L=len[now]+1,mx[i]=L;now=son[now][S[i]-'0'];
}
}
inline int check(int mid)
{
int h=1,t=0;
for(re int i=0;i<=n;i++) q[i]=dp[i]=0;
for(re int i=mid;i<=n;i++)
{
while(h<=t&&dp[i-mid]-i+mid>dp[q[t]]-q[t]) t--;
q[++t]=i-mid;
while(h<=t&&i-mx[i]>q[h]) h++;
if(h<=t) dp[i]=i+dp[q[h]]-q[h];
dp[i]=max(dp[i],dp[i-1]);
}
if(dp[n]*10>=n*9) return 1;
return 0;
}
int main()
{
scanf("%d%d",&T,&m);
for(re int i=1;i<=m;i++)
{
scanf("%s",S+1); int L=strlen(S+1);
for(re int j=1;j<=L;j++) ins(S[j]-'0');
ins(2);
}
while(T--)
{
scanf("%s",S+1),n=strlen(S+1);
query();
int l=1,r=n,ans=0;
while(l<=r)
{
int mid=l+r>>1;
if(check(mid)) ans=mid,l=mid+1;else r=mid-1;
}
printf("%d
",ans);
}
return 0;
}