【BZOJ2806】熟悉的文章(CTSC2012)-广义SAM+二分+DP+单调队列

测试地址:熟悉的文章
做法:本题需要用到广义SAM+二分+DP+单调队列。
首先,L的性质显然是单调的,所以我们二分L。接下来容易想到DP,令f(i)为以第i个字符结尾的前缀最多能有多少个字符被符合条件的子串覆盖,容易得到状态转移方程:
f(i)=max(f(i1),f(ik)+k)
其中f(i1)的转移表示我们不选择用一个子串覆盖一个后缀,而f(ik)+k的转移需要保证ik+1i这一段为M个模式串的一个子串,并且kL
要判别一个串是不是M个模式串的一个子串,显然使用好写又快的广义SAM,这样我们就可以在DP时顺便将字符串在SAM中匹配,并求出它最长的满足条件的后缀长度。注意满足要求的后缀长度不一定是对应节点能表示的最长的一个子串,因为每次匹配如果匹配上了,最长长度只会加1,不一定会加满。令前i个字符构成的前缀的满足条件的最长长度为len(i),显然长度在len(i)之内的后缀都满足条件。因此上面DP方程的条件可以简写为:
Lklen(i)
也即:
ilen(i)ikiL
注意到合法的转移构成了一个连续的区间,而这个区间的左右端点都是单调不递减的(ilen(i)一定不递减,因为若i1len(i1)>ilen(i),有len(i)>len(i1)+1,根据定义明显知道不可能出现这种情况),所以可以使用单调队列优化DP。于是我们就完成了这一题,时间复杂度为O(nlogn)
以下是本人代码:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
int n,m,rt=1,last,tot=1;
int ch[2200010][2]={0},pre[2200010]={0},len[2200010]={0};
int q[1100010],f[1100010];
char s[1100010];

void make_clone(int c,int p,int q,int nq)
{
    len[nq]=len[p]+1;
    ch[nq][0]=ch[q][0];
    ch[nq][1]=ch[q][1];
    while(ch[p][c]==q) ch[p][c]=nq,p=pre[p];
    pre[nq]=pre[q];
    pre[q]=nq;
}

void extend(int c)
{
    int p,q,np;
    p=last;
    if (ch[p][c])
    {
        if (len[ch[p][c]]==len[p]+1) last=ch[p][c];
        else make_clone(c,p,ch[p][c],++tot),last=tot;
        return;
    }

    np=++tot;
    len[np]=len[p]+1;
    while(!ch[p][c]) ch[p][c]=np,p=pre[p];
    if (!p) pre[np]=rt;
    else
    {
        q=ch[p][c];
        if (len[q]==len[p]+1) pre[np]=q;
        else make_clone(c,p,q,++tot),pre[np]=tot;
    }
    last=np;
}

bool check(int l,int n)
{
    f[0]=0;
    int h=1,t=0,now=rt,nowlen=0;
    for(int i=1;i<=n;i++)
    {
        while(now&&!ch[now][s[i]-'0']) now=pre[now],nowlen=len[now];
        if (!now) now=rt;
        else now=ch[now][s[i]-'0'],nowlen++;
        if (i>=l)
        {
            while(h<=t&&f[q[t]]-q[t]<=f[i-l]-(i-l)) t--;
            q[++t]=i-l;
        }
        while(h<=t&&q[h]<i-nowlen) h++;
        if (h>t) f[i]=f[i-1];
        else f[i]=max(f[i-1],f[q[h]]+i-q[h]);
    }
    return 10*f[n]>=9*n;
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%s",s);
        int len=strlen(s);
        last=rt;
        for(int j=0;j<len;j++)
            extend(s[j]-'0');
    }

    for(int i=1;i<=n;i++)
    {
        scanf("%s",s+1);
        s[0]='#';
        int len=strlen(s)-1;
        int l=0,r=len;
        while(l<r)
        {
            int mid=(l+r)>>1;
            if (check(mid+1,len)) l=mid+1;
            else r=mid;
        }
        printf("%d\n",l);
    }

    return 0;
}
posted @ 2018-05-27 15:56  Maxwei_wzj  阅读(90)  评论(0编辑  收藏  举报