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

24 篇文章 0 订阅
13 篇文章 0 订阅

题目:

我是超链接

题解:

我们先把标准串建出一个广义后缀自动机
二分一个L,用dp判断可行性,dp?!

首先我们要用后缀自动机预处理出l[i],表示第i位一定选,可以匹配上的最长长度,即向前延伸最远可以和标准串匹配的长度

怎么用dp判可行啊?f[i]表示前i位能称为【熟悉】的最大长度,那么最后用f[n]和len比一比看看到不到90%就好,f[i]怎么求呢?
不难写出转移方程f[i]=max{f[i-1],f[j]+i-j}         j[il[i],iL] j ∈ [ i − l [ i ] , i − L ]
这个范围是需要我们注意的,i-l[i]是因为过了这个界就不能匹配了,也就是不能用i-j来表示长度了;i-L是被称为【熟悉】的限制

O(n2) O ( n 2 ) ?不行啊我们还是优化下看看能不能快一点
这个f[i]=f[i-1]可以赋为初值,当然如果i < L的话就不可能被称为【熟悉】了直接过
l[i]+1>=l[i+1] l [ i ] + 1 >= l [ i + 1 ]

i+1l[i]1<=i+1l[i+1] i + 1 − l [ i ] − 1 <= i + 1 − l [ i + 1 ]

il[i]<=i+1l[i+1] i − l [ i ] <= i + 1 − l [ i + 1 ]

所以i-l[i]是单调不降的,我们可以用单调队列维护区间f[i]-i的最值。

我们之前已经说过了,i结点的转移区间只有 [i−l[i],i−L]
每当i在向后移动的时候,唯一可能产生的新的转移点就是(i−L)
那我们就在队尾插入这个转移点:i−L

记住要先加入一个新点然后再处理head,why?因为i-l[i]不一定比i-L小,把head放在后面又处理掉一批不合法的情况。

信心满满交了上去,华丽丽的M了。。。。看到只有0/1之后眼泪掉下来

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int N=2200005;
int np,p,nq,q,last,cnt,ch[N][2],fa[N],step[N],l[N],len,f[N],que[N];
char st[N];
void insert(int c)
{
    p=last; np=last=++cnt;
    step[np]=step[p]+1;
    while (p && !ch[p][c]) ch[p][c]=np,p=fa[p];
    if (!p) {fa[np]=1;return;}
    q=ch[p][c];
    if (step[q]==step[p]+1){fa[np]=q;return;}
    nq=++cnt; step[nq]=step[p]+1;
    memcpy(ch[nq],ch[q],sizeof(ch[q]));
    fa[nq]=fa[q]; fa[q]=fa[np]=nq;
    while (ch[p][c]==q) ch[p][c]=nq,p=fa[p];
}
void init()
{
    p=1;int tmp=0;
    for (int i=1;i<=len;i++)
    {
        int c=st[i]-'0';
        if (ch[p][c]) p=ch[p][c],tmp++;
        else
        {
            while (p && !ch[p][c]) p=fa[p];
            if (!p) p=1,tmp=0;
            else tmp=step[p]+1,p=ch[p][c];
        }
        l[i]=tmp;
    }
}
bool check(int L)
{
    if (L==0) return 1;
    f[0]=0;
    int head=1,tail=0;
    for (int i=1;i<=len;i++)
    {
        f[i]=f[i-1];
        if (i<L) continue;      
        while (head<=tail && f[que[tail]]-que[tail]-L<=f[i-L]-i) tail--;
        que[++tail]=i-L;
        while (head<=tail && que[head]<i-l[i]) head++;
        if (head<=tail) f[i]=max(f[i],f[que[head]]+i-que[head]);
    }
    return f[len]*10>=len*9;
}
void solve()
{
    init();
    int l=0,r=len,ans;
    while (l<=r)
    {
        int mid=(l+r)>>1;
        if (check(mid)) l=mid+1,ans=mid;
        else r=mid-1;
    }
    printf("%d\n",ans);
}
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    cnt=1;
    for (int i=1;i<=m;i++)
    {
        scanf("%s",st+1);
        int l=strlen(st+1);last=1;
        for (int j=1;j<=l;j++) insert(st[j]-'0');
    }
    for (int i=1;i<=n;i++)
    {
        scanf("%s",st+1);
        len=strlen(st+1);
        solve();
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值