BZOJ 2806 Cheat(SAM+DP)

题目链接:http://61.187.179.132/JudgeOnline/problem.php?id=2806

题意:

BZOJ 2806 Cheat(SAM+DP)

 

思路:首先将字典串插入SAM。之后就是一个DP。设给出的串为S。由于单调性,我们首先二分答案L。我们用d[i]表示从i向前最大长度的S子串使得该子串为在字典中,那么有:

用f[i]表示到S[i]能匹配到的最大值,那么有:

BZOJ 2806 Cheat(SAM+DP)

下面我们证明i-d[i]单调不减:如果有某个i,使得i-d[i]>i+1-d[i+1],由d[i]的意义知S[i-d[i]+1,i]和S[i+1-d[i+1]+1,i+1]是字典中某个串的子串。由于i-d[i]+1>i+1-d[i+1]+1,知S[i+1-d[i+1]+1,i]也是字典中某个串的子串且比S[i-d[i]+1,i]更长,这与d[i]的定义中的max违背。因此i-d[i]<=i+1-d[i+1]。这样的话决策点是单调的,可以用单调队列维护。因此上面的式子我们用g[j]=f[j]-j。

 

struct SAM
{
    SAM *son[3],*pre;
    int len;
};


SAM sam[N],*head,*last;
int cnt;


void initSam()
{
    head=last=&sam[0];
    cnt=1;
}


void insert(int x)
{
    SAM *p=&sam[cnt++],*u=last;
    p->len=last->len+1;
    last=p;
    for(;u&&!u->son[x];u=u->pre) u->son[x]=p;
    if(!u) p->pre=head;
    else if(u->son[x]->len==u->len+1) p->pre=u->son[x];
    else
    {
        SAM *r=&sam[cnt++],*q=u->son[x];
        *r=*q; r->len=u->len+1;
        p->pre=q->pre=r;
        for(;u&&u->son[x]==q;u=u->pre) u->son[x]=r;
    }
}


int n,m,len,M;
char s[N];
int d[N];


void init()
{
    SAM *p=head;
    int i,cur=0,x;
    FOR1(i,M)
    {
        x=s[i-1]-48;
        if(p->son[x]) cur++,p=p->son[x];
        else
        {
            while(p&&!p->son[x]) p=p->pre;
            if(!p) p=head,cur=0;
            else cur=p->len+1,p=p->son[x];
        }
        d[i]=cur;
    }
}


int f[N],g[N],Q[N];


int OK(int L)
{
    int head=1,tail=0,i,w;
    FOR1(i,M)
    {
        w=i-L;
        if(w>=0)
        {
            while(head<=tail&&g[Q[tail]]<g[w]) tail--;
            Q[++tail]=w;
        }
        while(head<=tail&&Q[head]<i-d[i]) head++;
        f[i]=f[i-1];
        if(head<=tail&&f[i]<g[Q[head]]+i) f[i]=g[Q[head]]+i;
        g[i]=f[i]-i;
    }
    return f[M]>=len;
}


int main()
{
    initSam();
    RD(n,m);
    int i;
    while(m--)
    {
        RD(s);
        for(i=0;s[i];i++) insert(s[i]-48);
        if(m) insert(2);
    }
    int low,high,mid,ans;
    while(n--)
    {
        RD(s);  M=strlen(s);
        len=ceil(M*0.9);
        init();
        low=1,high=M;
        while(low<=high)
        {
            mid=(low+high)/2;
            if(OK(mid)) low=mid+1;
            else high=mid-1;
        }
        if(low<=M&&OK(low)) PR(low);
        else PR(high);
    }
}

 

你可能感兴趣的:(ZOJ)