【[CTSC2012]熟悉的文章】

题目

好题啊

\(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\n",ans);
	}
	return 0;
}
posted @ 2019-01-05 15:47  asuldb  阅读(130)  评论(0编辑  收藏  举报