bzoj2806 [Ctsc2012]Cheat(单调队列优化dp+二分+广义SAM)

64 篇文章 0 订阅
23 篇文章 0 订阅

题目链接

分析:
一看到这种最大值的问题,冥冥之中可以感知到是二分在召唤我们

我们把所有的标准串扔到 SAM S A M 里,建出一个广义后缀自动机
二分一个 L L ,用dp判断可行性
怎么用dp呢dp呢dp呢

其实这个问题,就是求把文章分段后的最大的匹配长度
我们先把模式串放到SAM上跑一遍
得到 len l e n 数组,表示 i i 位置之前最大公共长度
得到这个有什么用呢?

设计DP状态:f[i]表示前 i i 个字符最长熟悉长度
对于当前我们考虑的状态,我们只要考虑它为某一段尾部最后一个字符就可以了

首先,如果当前位置的撑破天都达不到L,我们就没有必要转移这个位置

其次, f[i]=f[i1] f [ i ] = f [ i − 1 ] ,因为第 i i 位的匹配长度肯定不会小于前一个位置的匹配长度

再其次,当前状态的决策区间(上一个区间的结尾)只可能是[ilen,iL]
因为小于 ilen i − l e n 的位置是不能匹配
那么我们很容易就可以得到转移: f[i]=max(f[i1],f[j]+ij|ilen[i]<=j<=iL) f [ i ] = m a x ( f [ i − 1 ] , f [ j ] + i − j | i − l e n [ i ] <= j <= i − L )
其中 [j+1,i] [ j + 1 , i ] 就是这一段的匹配长度

但是这样是 O(n2) O ( n 2 ) 的复杂度,我们能不能优化呢

因为 ilen[i] i − l e n [ i ] 严格单调不减,也就是说转移点单调不减,所以我们可以用单调队列优化
双端队列
  • 队尾
    强调一点:队列中的元素一定是能够转移此结点的状态
    我们之前已经说过了, i i 结点的转移区间只有 [ilen[i],iL]
    每当 i i 在向后移动的时候,唯一可能产生的新的转移点就是(iL)
    那我们就在队尾插入这个转移点: (iL) ( i − L )
    观察一下转移方程: f[i]=f[j]+ij f [ i ] = f [ j ] + i − j
    由转移点决定的值只有: f[j]j f [ j ] − j
    所以我们直接对比这个值即可,保证队列中单调不增
while (tou<=wei&&f[q[wei]]-q[wei]<f[i-x]-(i-x)) wei--;
  • 队首:判断队首的点是否在决策区间内
    因为队列是单调不增的,所以队首状态就一定是最优的了: f[i]=max(f[i],f[q.front]+iq.front) f [ i ] = m a x ( f [ i ] , f [ q . f r o n t ] + i − q . f r o n t )

最后,判断是否 f[n] f [ n ] 是否满足90%的条件即可

tip

注意 f[0] f [ 0 ] 也是一个转移状态

insert的时候++sz写错了。。。mmp

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>

using namespace std;

const int N=1100003;

int n,m,ch[N<<1][2],dis[N<<1],fa[N<<1],last=1,root=1,sz=1,len;
int L[N],f[N],q[N<<1];
char s[N];

void insert(int x)
{
    int now=++sz,pre=last;
    last=now;
    dis[now]=dis[pre]+1;
    for (;pre&&!ch[pre][x];pre=fa[pre]) ch[pre][x]=now;
    if (!pre) fa[now]=root;
    else {
        int q=ch[pre][x];
        if (dis[q]==dis[pre]+1) fa[now]=q;
        else
        {
            int nows=++sz;
            dis[nows]=dis[pre]+1;
            memcpy(ch[nows],ch[q],sizeof(ch[q]));
            fa[nows]=fa[q]; fa[q]=fa[now]=nows;
            for (;pre&&ch[pre][x]==q;pre=fa[pre]) ch[pre][x]=nows; 
        }
    }
}

void get()
{
    int now=root,sum=0;
    for (int i=1;i<=len;i++)    //在SAM上匹配 
    {
        int x=s[i]-'0';
        if (ch[now][x]!=0) 
            now=ch[now][x],sum++; 
        else
        {
            while (now&&!ch[now][x]) now=fa[now];
            if (!now) now=root,sum=0;
            else sum=dis[now]+1,now=ch[now][x];
        }
        L[i]=sum;
    }
}

bool check(int x)   
{
    int tou=1,wei=0;
    for (int i=1;i<=len;i++)
    {
        f[i]=f[i-1];
        if (i-x<0) continue;     //此状态没有转移意义 
        while (tou<=wei&&f[q[wei]]-q[wei]<f[i-x]-i+x) wei--;
        q[++wei]=i-x;
        while (tou<=wei&&q[tou]<i-L[i]) tou++;   //决策区间 
        if (tou<=wei) f[i]=max(f[i],f[q[tou]]+i-q[tou]);
    }
    return f[len]*10>=len*9;
}

int solve()
{
    get();
    int l=0,r=len,ans=0;
    while (l<=r)
    {
        int mid=(l+r)>>1;
        if (check(mid)) ans=max(ans,mid),l=mid+1;
        else r=mid-1;
    }
    return ans;
}

int main()
{
    scanf("%d%d",&n,&m);
    for (int i=1;i<=m;i++)
    {
        scanf("%s",s+1);
        last=root; len=strlen(s+1);
        for (int j=1;j<=len;j++) insert(s[j]-'0');
    }
    for (int i=1;i<=n;i++)
    {
        scanf("%s",s+1);
        len=strlen(s+1);
        printf("%d\n",solve());
    }
    return 0;
} 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值