bzoj2806 【Ctsc2012】 Cheat 後綴自動機+單調隊列優化dp

題目大意:
給定一些標準串。
給定一個串,要求一個L使得在這個序列中可以取出若干個長度超過L的子串(這些子串必須是給定標準串的子串),且取出的串的總長度超過整個串的90%。
求最大的L。

題目分析:

先用後綴自動機求出以每個位置爲結尾往前最多能匹配多少位,我們用數組a[i]來存儲這個值。
這個只要對於所有的標準串建一個廣義的後綴自動機,然後把這個串放進去跑一遍,如果能匹配的話,這一位就比上一位多匹配一位,否則跳parent,並且將當前匹配上的最大長度設爲當前節點的max_len再繼續匹配(詳見代碼。

然後我們要求L最小值最大,這種問題可以想到二分。
問題轉化成了給定L,求是否能取出大於等於90%的子串使之滿足條件。
這樣我們設f[i]表示匹配到第i個位置匹配不上的字符有多少個。
那麼轉移很顯然就是Min{f[i-1]+1,f[(i-a[i])~f[i-L]]};
這樣的話我們很顯然有一個時間複雜度上界是n^2的判定方法。
這樣的時間複雜度不能接受,但是我們可以發現,i-a[i]是單調遞增的,i-L也是單調遞增的,那麼我們可以用一個單調隊列來維護這個這個這個值。
這樣時間複雜度就降到了O(n)
算上二分,O(nlog(n))

代碼如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 1200000
using namespace std;
int n,m;
int len;
char s[N];
int f[N],a[N],dl[N];
struct SAM{
    SAM *son[2],*fa;
    int max_len;
    SAM(int _):max_len(_)
    {
        memset(son,0,sizeof(son));
        fa=NULL;
    }
}*root=new SAM(0),*last=root;
void extend(int x)
{
    SAM *p=last;
    SAM *np=new SAM(p->max_len+1);
    while(p && !p->son[x]) p->son[x]=np,p=p->fa;
    if(!p) np->fa=root;
    else
    {
        SAM *q=p->son[x];
        if(p->max_len+1==q->max_len) np->fa=q;
        else
        {
            SAM *nq=new SAM(p->max_len+1);
            nq->fa=q->fa;
            memcpy(nq->son,q->son,sizeof(nq->son));
            q->fa=nq; np->fa=nq;
            for(;p && p->son[x]==q;p=p->fa) p->son[x]=nq;
        }
    }
    last=np;
}
bool check(int L)
{
    int l=1,r=0;
    f[0]=0;
    for(int i=1;i<=len;i++)
    {
        f[i]=f[i-1]+1;
        if(i-L>=0)
        {
            while(r>=l && f[i-L]<=f[dl[r]]) r--;
            dl[++r]=i-L;
        }
        while(r>=l && dl[l]<i-a[i]) l++;
        if(r>=l) f[i]=min(f[i],f[dl[l]]);
    }
    return f[len]*10<=len;
}
int main()
{
    scanf("%d%d",&n,&m);
    while(m--)
    {
        last=root;
        scanf("%s",s);
        for(int i=0;s[i];i++)
            extend(s[i]-'0');
    }
    while(n--)
    {
        scanf("%s",s+1);
        len=strlen(s+1);
        int now=0,x;
        SAM *c=root;
        for(int i=1;i<=len;i++)
        {
            x=s[i]-'0';
            while(c!=root && !c->son[x])
            {
                now=c->fa->max_len;
                c=c->fa;
            }
            if(c->son[x])
            {
                c=c->son[x];
                now++;
            }
            a[i]=now;
        }
        int l=0,r=len,ans=0;
        while(l<=r)
        {
            int mid=l+r>>1;
            if(check(mid)) ans=mid,l=mid+1;
            else r=mid-1;
        }
        printf("%d\n",ans);
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章