BZOJ 2806: [Ctsc2012]Cheat(单调队列优化dp+后缀自动机)

本文深入解析了使用广义后缀自动机解决字符串匹配问题的方法,并通过优化动态规划算法实现高效求解。文章详细介绍了如何构建广义后缀自动机,以及如何利用单调队列进行优化,达到O(nlogn)的时间复杂度。

传送门

解题思路

  肯定先要建出来广义后缀自动机。刚开始以为是个二分+贪心,写了一下结果\(20\)分。说一下正解,首先显然\(L_0\)具有单调性,是可以二分的。考虑二分后怎样判合法,对于分割序列很容易想到\(dp\),设\(f_i\)表示前\(i\)个字符匹配成功数量,那么有转移方程\(f_i=max(f_j+i-j)(i-j>=L\)\(j\)\(i\)可以匹配 \()\)\(L\)是二分出来的限制,判断是否能匹配可以预处理,预处理出\(mth_i\)表示\(i\)最多能与往前\(mth_i\)位匹配成功,那么第二个条件就变成了\(j>=i-mth_i\)。如果这样做是\(O(n^2logn)\)的,实测可以拿到\(75\)分2333。考虑优化,发现\(i-mth_i\)具有单调性,因为每移动一格\(i\)\(+1\),而\(mth_i\)最多\(+1\)。那么可以用一个单调递减队列来优化,每次将\(i-lim\)入队,取出队头更新答案,时间复杂度为\(O(nlogn)\)

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>

using namespace std;
const int N=1100005<<1;

int n,m,res,ans,f[N],mth[N],q[N],hd,tl;
char s[N];

struct SAM{
    int ch[N][2],fa[N],l[N],lst,cnt;
    void Insert(int c){
        int p=lst,np=++cnt; l[np]=l[p]+1; lst=cnt;
        for(;p && !ch[p][c];p=fa[p]) ch[p][c]=np;
        if(!p) fa[np]=1;
        else {
            int q=ch[p][c]; 
            if(l[p]+1==l[q]) fa[np]=q;
            else {
                int nq=++cnt; l[nq]=l[p]+1;
                memcpy(ch[nq],ch[q],sizeof(ch[nq]));
                fa[nq]=fa[q]; fa[q]=fa[np]=nq;
                for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
            }
        }
    }
    void prework(int len){
        int now=1,num=0;
        for(int i=1;i<=len;i++){
            if(ch[now][s[i]-'0']) now=ch[now][s[i]-'0'],num++;
            else {
                for(;now && !ch[now][s[i]-'0'];now=fa[now]);
                if(!now) now=1,num=0;
                else num=l[now]+1,now=ch[now][s[i]-'0'];
            }
            mth[i]=num;
        }
    }
    bool check(int lim,int len){    
        int tmp; hd=1; tl=0; 
        for(int i=1;i<=len;i++){
            f[i]=f[i-1]; if(i<lim) continue; tmp=i-mth[i];
            while(hd<=tl && f[q[tl]]-q[tl]<=f[i-lim]-i+lim) tl--;
            q[++tl]=i-lim;
            while(hd<=tl && q[hd]<tmp) hd++;
            if(hd<=tl) f[i]=max(f[i],f[q[hd]]-q[hd]+i);
        }
//      for(int i=1;i<=len;i++){
//          f[i]=f[i-1];
//          for(int j=max(0,i-mth[i]);j+lim<=i;j++)
//              f[i]=max(f[i],f[j]+i-j);
//      }
        return len-f[len]>res?0:1;
    }
    void solve(int len){    
        prework(len);
        int L=1,R=len,mid;
        while(L<=R){
            mid=(L+R)>>1;
            if(check(mid,len)) L=mid+1,ans=mid;
            else R=mid-1;
        }
        printf("%d\n",ans);
    }
}sam;

int main(){
    scanf("%d%d",&n,&m); sam.cnt=1; int len;
    for(int i=1;i<=m;i++){
        scanf("%s",s+1); sam.lst=1;
        len=strlen(s+1);
        for(int j=1;j<=len;j++) sam.Insert(s[j]-'0');
    }
    for(int i=1;i<=n;i++){
        scanf("%s",s+1); len=strlen(s+1);
        res=(len*9+9)/10; res=len-res;
        sam.solve(len); ans=0;
    }
    return 0;
}   

转载于:https://www.cnblogs.com/sdfzsyq/p/10457823.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值