bzoj2806 Cheat 后缀自动机&单调队列

    做完CTSC的题目感觉整个人都不好了。。。

    首先将母串中间插入'2'然后连成一串建立后缀自动机。

    对于每一个询问:显然可以发现如果串s对于L如果是“熟悉的文章”,那么任意L'<=L必然是“熟悉的”,于是二分答案x,令f[i]表示到第i位的可以得到的熟悉的子串长度,可以得到方程:

    f[i]=max{f[j]+i-j},其中i-j>=L且s[i..j]在母串中出现。

    如果利用后缀自动机,我们可以均摊O(1)得到以i为右端点向左延伸的最远的距离,即j的下界;另外由i-j>=L→j<=i-L,即j的上界。另一方面,如果将f[j]-j作为一个整体,就可以用单调队列维护,于是可以均摊O(1)得到f[i]的值。

    最后判断f[i]/len(s[j])是否不小于0.9即可。

AC代码如下(写得好慢参考即可。如果把求j的下界作为一个独立的函数去求就会快得多了(不要问我为什么)):

#include<iostream>
#include<cstdio>
#include<cstring>
#define N 2250005
using namespace std;

int n,m,tot,last,len,ch[N][3],mx[N],fa[N],f[N],q[N]; char s[N];
void extend(int x){
	int p=last,np=last=++tot; mx[np]=mx[p]+1; 
	for (; p && !ch[p][x]; p=fa[p]) ch[p][x]=np;
	if (!p) fa[np]=1; else{
		int q=ch[p][x];
		if (mx[p]+1==mx[q]) fa[np]=q; else{
			int nq=++tot; mx[nq]=mx[p]+1; fa[nq]=fa[q];
			memcpy(ch[nq],ch[q],sizeof(ch[q]));			
			for (fa[np]=fa[q]=nq; ch[p][x]==q; p=fa[p]) ch[p][x]=nq;
		}
	}
}
bool ok(int x){
	int i,head=1,tail=0,now=1,tmp=0;
	for (i=1; i<=len; i++){
		int c=s[i]-'0',j=i-x; f[i]=f[i-1];
		while(now && !ch[now][c]) now=fa[now];
		tmp=min(tmp,mx[now])+1; now=(now)?ch[now][c]:1;
		if (j>=0){ while (head<=tail && f[j]-j>=f[q[tail]]-q[tail]) tail--; q[++tail]=j; }
		while (head<=tail && q[head]<i-tmp) head++;
		if (head<=tail) f[i]=max(f[i],f[q[head]]+i-q[head]);
	}
	return f[len]*10>=len*9;
}
int main(){
	scanf("%d%d",&n,&m); int i,j; tot=last=1;
	for (i=1; i<=m; i++){
		scanf("%s",s); for (j=0; s[j]; j++) extend(s[j]-'0'); extend(2);
	}
	for (i=1; i<=n; i++){
		scanf("%s",s+1); len=strlen(s+1); int l=0,r=len;
		while (l+1<r){
			int mid=(l+r)>>1; if (ok(mid)) l=mid; else r=mid-1;
		}
		if (ok(r)) printf("%d\n",r); else printf("%d\n",l);
	}
	return 0;
}

by lych

2016.2.7

你可能感兴趣的:(动态规划,二分,后缀自动机,单调队列)