题解-CTSC2012 熟悉的文章

Problem

bzoj

题目大意:给定多个标准串和一个文本串,全部为01串,如果一个串长度不少于\(L\)且是任意一个标准串的子串,那么它是“熟悉”的。对于文本串\(A\),把\(A\)分割成若干段子串,其中“熟悉”的子串的长度总和不少于\(A\)总长度的\(90\%\),那么该\(L\)是可行的。求可行的\(L\)最大值

Solution

前置技能:二分答案、SAM、Dp、单调队列

字符串长在L上下对答案贡献是断崖式的,按套路二分L

再根据对序列分段问题的直觉可以得到dp方程:\(f[i]=max(f[i-1],f[j]+i-j),j\leq i-L且s[j…i]是熟悉的\)

这样复杂度加上各种优化是\(O(n^2)\)\(O(n^3)\)不等的

考虑到对于\(i\),合法的\(j\)一定是连续的,可以用后缀自动机预处理出每一个字符\(i\)向左最长的熟悉的串位置\(orz[i]\)

dp方程为:\(f[i]=\max(f[i-1],f[j]+i-j),j\in[i-orz[i],i-L]\)

发现dp方程可以用单调队列优化:队列里存\(f[i]-i\)即可

Code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rg register

const int N=2001000;
int pre[N],stp[N],ch[N][2];
int f[N],q[N],orz[N];
int n,m,len,tot=1,lst=1,L,R;
char s[N];

inline void ins(int x){
	int p=lst,np=++tot;
	lst=np;stp[np]=stp[p]+1;
	while(p&&!ch[p][x])ch[p][x]=np,p=pre[p];
	if(!p)pre[np]=1;
	else {
		int q=ch[p][x];
		if(stp[q]==stp[p]+1)pre[np]=q;
		else {
			int nq=++tot;stp[nq]=stp[p]+1;
			//*ch[nq]=*ch[q];
			ch[nq][0]=ch[q][0],ch[nq][1]=ch[q][1];
			pre[nq]=pre[q];
			pre[q]=pre[np]=nq;
			while(ch[p][x]==q)ch[p][x]=nq,p=pre[p];
		}
	}return ;
}

inline int check(int li){
	int he(1),ta(0);
	for(rg int i=1;i<=len;++i){
		f[i]=f[i-1];
		if(i<li)continue;
		while(he<=ta&&f[q[ta]]-q[ta]<=f[i-li]-i+li)--ta;
		q[++ta]=i-li;
		while(he<=ta&&q[he]<i-orz[i])++he;
		if(he<=ta)f[i]=max(f[i],f[q[he]]+i-q[he]);
	}return f[len]*10>=len*9;
}

void PRE(){
	scanf("%s",s+1);
	len=strlen(s+1);
	L=0;R=len;
	int nw(1),cnt(0);
	for(rg int i=1;i<=len;++i){
		int x=s[i]-'0';
		if(ch[nw][x])++cnt,nw=ch[nw][x];
		else {
			while(nw&&!ch[nw][x])nw=pre[nw];
			if(nw)cnt=stp[nw]+1,nw=ch[nw][x];
			else nw=1,cnt=0;
		}
		orz[i]=cnt;
	}return ;
}

int main(){
	scanf("%d%d",&n,&m);
	while(m--){
		scanf("%s",s);lst=1;
		for(rg int i=0;s[i];++i)ins(s[i]-'0');
	}
	while(n--){
		PRE();
		while(L<R){
			int mid(L+R+1>>1);
			if(check(mid))L=mid;
			else R=mid-1;
		}
		printf("%d\n",L);
	}return 0;
}
posted @ 2018-06-27 13:29  oier_hzy  阅读(202)  评论(0编辑  收藏  举报