1. 程式人生 > >【BZOJ2806】[Ctsc2012]Cheat 廣義後綴自動機+二分+單調隊列優化DP

【BZOJ2806】[Ctsc2012]Cheat 廣義後綴自動機+二分+單調隊列優化DP

geo soft -1 文本 i++ else jpg light hint

【BZOJ2806】[Ctsc2012]Cheat

Description

技術分享

技術分享

Input

第一行兩個整數N,M表示待檢查的作文數量,和小強的標準作文庫
的行數
接下來M行的01串,表示標準作文庫
接下來N行的01串,表示N篇作文

Output

N行,每行一個整數,表示這篇作文的Lo 值。

Sample Input

1 2
10110
000001110
1011001100

Sample Output

4

HINT

輸入文件不超過1100000字節

註意:題目有改動,可識別的長度不小於90%即可,而不是大於90%

題解:顯然答案是可二分的。但是二分之前,我們還需要知道字符串中每個位置往前最多能匹配多少,由於是多個文本串,所以我們要用廣義SAM。

這其實是另一道spoj的題(不過我沒做),其實做法也不難。我們將目標串在SAM上跑一遍,如果在某個節點失配了,即ch[x][a]=0,那麽我們就讓x沿著parent指針已知向上跳,知道匹配上為止,然後令sum=mx[x]+1。如果沒有失配,則sum++。(註意,ch[x][a]指向的點的mx不一定=mx[x]+1,實際上,那個點的mx=mx[x在parent樹上的某個子孫]+1)

然後我們設f[i]表示位置i能往前匹配的最長長度,那麽有如下DP方程:

$f[i]=max(f[i-1],f[j]+i-j)|i-sm[i]\le j \le i-L_0$

顯然我們用單調隊列維護f[j]-j的最大值就行了。

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
int n,m,tot,len;
const int maxn=2200010;
int pre[maxn],ch[maxn][2],mx[maxn],sm[maxn],f[maxn],q[maxn];
char str[maxn];
int extend(int x,int p)
{
	int np=++tot;
	mx[np]=mx[p]+1;
	for(;p&&!ch[p][x];p=pre[p])	ch[p][x]=np;
	if(!p)	pre[np]=1;
	else
	{
		int q=ch[p][x];
		if(mx[q]==mx[p]+1)	pre[np]=q;
		else
		{
			int nq=++tot;
			pre[nq]=pre[q],pre[np]=pre[q]=nq,mx[nq]=mx[p]+1;
			memcpy(ch[nq],ch[q],sizeof(ch[q]));
			for(;p&&ch[p][x]==q;p=pre[p])	ch[p][x]=nq;
		}
	}
	return np;
}
void match()
{
	int i,u,a,sum=0;
	for(u=i=1;i<=len;i++)
	{
		a=str[i]-‘0‘;
		if(ch[u][a])	sum++,u=ch[u][a];
		else
		{
			for(;u&&!ch[u][a];u=pre[u]);
			if(!u)	sum=0,u=1;
			else	sum=mx[u]+1,u=ch[u][a];
		}
		sm[i]=sum;
	}
}
bool solve(int sta)
{
	int i,j,h=1,t=0;
	for(i=0;i<=len;i++)	f[i]=0;
	for(i=sta-1;i<=len;i++)
	{
		while(h<=t&&q[h]<i-sm[i])	h++;
		f[i]=f[i-1];
		if(sm[i]>=sta)	f[i]=max(f[i],f[q[h]]-q[h]+i);
		j=i-sta+1;
		while(h<=t&&f[q[t]]-q[t]<=f[j]-j)	t--;
		q[++t]=j;
	}
	return f[len]*10>=len*9;
}
int main()
{
	scanf("%d%d",&n,&m);
	int i,j,a,b,l,r,mid;
	for(tot=i=1;i<=m;i++)
	{
		scanf("%s",str),a=strlen(str);
		for(b=1,j=0;j<a;j++)	b=extend(str[j]-‘0‘,b);
	}
	for(i=1;i<=n;i++)
	{
		scanf("%s",str+1),len=strlen(str)-1;
		match();
		l=1,r=len+1;
		while(l<r)
		{
			mid=l+r>>1;
			if(solve(mid))	l=mid+1;
			else	r=mid;
		}
		printf("%d\n",l-1);
	}
	return 0;
}

【BZOJ2806】[Ctsc2012]Cheat 廣義後綴自動機+二分+單調隊列優化DP