bzoj 2806: [Ctsc2012]Cheat【广义SAM+二分+dp+单调队列】

把模板串建一个广义SAM
然后在线查询,每次在SAM上预处理出一个a[i]表示i位置向前最多能匹配多长的模板串
二分答案L,dp判断,设f[i]为·~i有几个匹配,转移显然是f[i]=max{f[i-1],f[j]+i-j(i-a[i]<=j<=i-L)},根据性质,i-a[i]是单调的(或者直接看a[i]的预处理过程也能看出来),所以这个dp可以用单调队列优化转移,最后判断是否f[n]>=L*0.9

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=3000005;
int n,m,fa[N],ch[N][2],dis[N],cur=1,con=1,la,a[N],q[N],f[N];
char s[N];
void ins(int c,int id)
{
	la=cur,dis[cur=++con]=id;
	int p=la;
	for(;p&&!ch[p][c];p=fa[p])
		ch[p][c]=cur;
	if(!p)
		fa[cur]=1;
	else
	{
		int q=ch[p][c];
		if(dis[q]==dis[p]+1)
			fa[cur]=q;
		else
		{
			int nq=++con;
			dis[nq]=dis[p]+1;
			memcpy(ch[nq],ch[q],sizeof(ch[nq]));
			fa[nq]=fa[q];
			fa[q]=fa[cur]=nq;
			for(;ch[p][c]==q;p=fa[p])
				ch[p][c]=nq;
		}
	}
}
bool ok(int w,int n)
{
	int l=1,r=0;//cerr<<w<<endl;
	for(int i=1;i<=n;i++)
	{
		f[i]=f[i-1];
		while(l<=r&&q[l]<i-a[i])
			l++;
		if(l<=r)
			f[i]=max(f[i],f[q[l]]+i-q[l]);
		if(i-w+1>=0)
		{
			while(l<=r&&f[q[r]]-q[r]<=f[i-w+1]-(i-w+1))
				r--;
			q[++r]=i-w+1;
		}//cerr<<i<<" "<<f[i]<<endl;
	}
	return f[n]*10>=n*9;
}
int main()
{
	scanf("%d%d",&m,&n);
	for(int j=1;j<=n;j++)
	{
		scanf("%s",s+1);
		cur=1;
		for(int i=1,len=strlen(s+1);i<=len;i++)
			ins(s[i]-'0',i);
	}
	while(m--)
	{
		scanf("%s",s+1);
		int len=strlen(s+1);
		for(int i=1,l=0,p=1;i<=len;i++)
		{
			int c=s[i]-'0';
			if(ch[p][c])
				l++,p=ch[p][c];
			else
			{
				for(;p&&!ch[p][c];p=fa[p]);
				if(!p)
					l=0,p=1;
				else
					l=dis[p]+1,p=ch[p][c];
			}
			a[i]=l;//cerr<<i<<" "<<a[i]<<endl;
		}
		int l=0,r=len,ans=0;
		while(l<=r)
		{
			int mid=(l+r)>>1;
			if(ok(mid,len))
				l=mid+1,ans=mid;
			else
				r=mid-1;
		}
		printf("%d
",ans);
	}
	return 0;
}
原文地址:https://www.cnblogs.com/lokiii/p/10009223.html