BZOJ 2806 [Ctsc2012]Cheat (后缀自动机+二分+单调队列+dp)

题目大意:

给你一堆模式串和文本串

对于每个文本串,我们可以把它不可重叠地拆分成很多子串,如果拆分出的串作为子串出现在了任何一个模式串中,我们称它是“眼熟的”,我们必须保证“眼熟的”子串总长度不小于文本串的90%,现在定义一个数$L$,表示拆分出的子串的最小长度,求每个文本串的$L$的最大值

神题

考虑$L$的性质,发现$L$越大,“眼熟的子串”总长度越长

可以这样简单证明,长度越小的串,对于匹配越有利,因为如果一个大串出现在了模式串中,那么它的所有子串一定出现在了模式串中,反之,小串出现在模式串中,几个小串组成的大串却不一定出现在模式串中。

发现了这个性质,我们可以就二分$L$了

每次选择一个长度$L$作为每次拆分出的串的长度下限进行验证

定义$f[i]$表示拆分串$S[1,i]$,拆分出的一些串能在模式串中被识别,这些能被识别的串的最长长度

要么第i位单独被拆出来,并且不被识别,$f[i]=f[i-1]$

要么第i位作为末尾,组成一个能被识别的串,必须保证开头的前一位$j\in[1,i-L]$,$f[i]=f[j]+i-j$

发现$f[i]=f[j]+i-j=(f[j]-j)+i$可以用单调队列优化

能被识别的串长度必须不小于$L$!

预处理,对所有模式串建广义$SAM$

每次把当前文本串放进去跑,预处理出以i为结尾的最长可识别串的长度$ma_{i}$

如果当前节点没有$trs[x][c]$,就要像$fail$树一样不断跳$pre$删掉一部分前缀,直到碰到一个节点有$trs[x][c]$

如果当前节点有$trs[x][c]$,就跳过去。

但现在我们先不能跳过去,因为$trs[x][c]$的信息我们还不知道

现在$dep_{x}$表示的并非当前串的长度,而是在$trs$图里表现的最长长度,由于每次沿$trs$指针移动,长度+1,所以$ma_{i}=min(ma_{i-1}+1,dep[x]+1)$

细节比较多,尤其是单调队列的地方

#include <cmath>
#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N1 1105000
#define S1 (N1<<1)
#define T1 (N1<<2)
#define ll long long
#define uint unsigned int
#define rint register int 
#define dd double
#define il inline 
#define inf 0x3f3f3f3f
#define idx(X) (X-'0')
using namespace std;

int gint()
{
    int ret=0,fh=1;char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')fh=-1;c=getchar();}
    while(c>='0'&&c<='9'){ret=ret*10+c-'0';c=getchar();}
    return ret*fh;
}
int N,M,len;
char str[N1];
int ma[N1];
namespace SAM{
int trs[S1][2],pre[S1],dep[S1],tot,la;
void init(){tot=la=1;}
void reduct(){la=1;}
void insert(int c)
{
    int p=la,np=++tot,q,nq;la=np;
    dep[np]=dep[p]+1;
    for(;p&&!trs[p][c];p=pre[p]) trs[p][c]=np;
    if(!p) {pre[np]=1;return;}
    q=trs[p][c];
    if(dep[q]==dep[p]+1) pre[np]=q;
    else{
        pre[nq=++tot]=pre[q];
        pre[q]=pre[np]=nq;
        dep[nq]=dep[p]+1;
        memcpy(trs[nq],trs[q],sizeof(trs[q]));
        for(;p&&trs[p][c]==q;p=pre[p]) trs[p][c]=nq;
    }
}
void get_ma()
{
    int x=1,c;
    for(int i=1;i<=len;i++)
    {
        c=idx(str[i]);
        for(;x&&!trs[x][c];x=pre[x]);
        if(!x){ma[i]=0,x=1;continue;}
        ma[i]=min(ma[i-1]+1,dep[x]+1);
        x=trs[x][c];
    }
}
};
int que[N1],f[N1];
int check(int L)
{
    int i,j,hd=1,tl=0;
    que[++tl]=0;
    for(i=1;i<L;i++) f[i]=0;
    for(i=max(1,L);i<=len;i++)
    {
        j=i-L;
        while(hd<=tl&&f[que[tl]]-que[tl]<=f[j]-j) tl--;
        que[++tl]=j;
        while(hd<=tl&&que[hd]<i-ma[i]) hd++;
        if(hd>tl) f[i]=f[i-1];
        else f[i]=max(f[i-1],f[que[hd]]+i-que[hd]);
    }
    if(10*f[len]>=9*len) return 1;
    else return 0;
}

int main()
{
    scanf("%d%d",&N,&M);
    int i,j,l,r,n,m,mid,mxl=0,ans;
    SAM::init();
    for(m=1;m<=M;m++)
    {
        scanf("%s",str+1);
        len=strlen(str+1);
        mxl=max(mxl,len);
        for(i=1;i<=len;i++) 
            SAM::insert(idx(str[i]));
        SAM::reduct();
    }
    for(n=1;n<=N;n++)
    {
        scanf("%s",str+1);
        len=strlen(str+1);
        SAM::get_ma();
        l=0,r=min(len,mxl),ans=0;
        while(l<=r){
            mid=(l+r)>>1;
            if(check(mid)) ans=mid,l=mid+1;
            else r=mid-1;
        }
        printf("%d\n",ans);
    }
    return 0;
}

 

posted @ 2018-12-14 17:03  guapisolo  阅读(188)  评论(0编辑  收藏  举报