CTSC2012-Cheat

题意

给出一些母01串,多次询问,每次询问一个01串,问一个最大的\(L\),使得可以在询问串中选出若干个不相交的,长度大于等于\(L\)的子串,这些子串都在母串中出现过,且子串的长度和大于等于询问串总长的\(90\%\)

文件大小小于等于1100000字节。

分析

首先如果一个\(L\)可行,那么小于\(L\)的也是可行的,因为是“长度大于等于”。于是我们就二分这个\(L\),转化成判定问题。

把分割序列这类问题可以考虑dp。设\(f_i\)为前\(i\)位能分割出来符合要求的最大子串长度和。显然有:
\[ f_i=\max \begin{cases} f_{i-1} \\ f_j+(i-j) && i-j\in [g_i,L] \end{cases} \]
第一种情况表示从前一个直接转移过来,即不以\(i\)结尾的。第二种表示以\(i\)结尾的,其中\(g_i\)表示第\(i\)位前面最多可以在母串中匹配多长。这可以通过广义后缀自动机方便地算出来(跳link重置为len,匹配加一)。

显然如果直接暴力dp的话是\(O(n^2)\)的,必须考虑优化。只考虑第二种情况:
\[ \begin{aligned} f_i=f_j+i-j && i-j\in [g_i,L] \\ f_i=i+(f_j-j) && j\in[i-g_i,i-L] \end{aligned} \]
可以注意到,\(i-L\)每次往后移动一格,而\(i-g_i\)的值是单调不减的,因为每次\(i\)加一,\(g_i\)最多加一,即最多多匹配一位,不可能突然多出来匹配的几位,否则就会与前面的\(g\)值矛盾。这就是说,\(j\)的可行区间是单调不减的,所以可以用单调队列优化到\(O(n)\)。队列为队头小,队尾大,每次在队头插入\(i-L\)处的值,如果队头比它小就弹出。在队尾把出了合法区间中的值弹出,取队尾即可。

单次询问的复杂度为\(O(len\log len)\)

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=1.1e6+10;
const int maxc=2;
char s[maxn];
int f[maxn],g[maxn],n,que[maxn],ql,qr;
struct SAM {
    int t[maxn<<1][maxc],len[maxn<<1],link[maxn<<1],last,tot;
    SAM ():tot(1) {}
    void reset() {last=1;}
    void add(int x) {
        if (t[last][x]) {
            int p=t[last][x];
            if (len[p]==len[last]+1) {
                last=p;
                return;
            } else {
                int q=++tot;
                len[q]=len[last]+1;
                memcpy(t[q],t[p],sizeof t[p]);
                for (int j=last;j && t[j][x]==p;j=link[j]) t[j][x]=q;
                link[q]=link[p],link[p]=q;
                last=q;
                return;
            }
        }
        int nw=++tot,i;
        len[nw]=len[last]+1;
        for (i=last;i && !t[i][x];i=link[i]) t[i][x]=nw;
        if (i) {
            int p=t[i][x];
            if (len[p]==len[i]+1) link[nw]=p; else {
                int q=++tot;
                len[q]=len[i]+1;
                memcpy(t[q],t[p],sizeof t[p]);
                for (int j=i;j && t[j][x]==p;j=link[j]) t[j][x]=q;
                link[q]=link[p],link[p]=link[nw]=q;
            }
        } else link[nw]=1;
        last=nw;
    }
    void prepare() {
        int now=1,mat=0;
        for (int i=1;i<=n;++i) {
            int x=s[i]-'0';
            while (now!=1 && !t[now][x]) now=link[now],mat=len[now];
            if (t[now][x]) now=t[now][x],++mat;
            g[i]=mat;
        }
    }
} sam;
bool dp(int L) {
    ql=1,qr=0;
    for (int i=L;i<=n;++i) {
        f[i]=f[i-1];
        while (ql<=qr && f[que[qr]]-que[qr]<f[i-L]-i+L) --qr;
        que[++qr]=i-L;
        while (ql<=qr && que[ql]<i-g[i]) ++ql;
        if (ql<=qr) f[i]=max(f[i],f[que[ql]]+i-que[ql]);
    }
    return 10*f[n]>=9*n;
}
int main() {
#ifndef ONLINE_JUDGE
    freopen("test.in","r",stdin);
#endif
    int q,m;
    scanf("%d%d",&q,&m);
    for (int i=1;i<=m;++i) {
        scanf("%s",s+1);
        int len=strlen(s+1);
        sam.reset();
        for (int i=1;i<=len;++i) sam.add(s[i]-'0');
    }
    while (q--) {
        scanf("%s",s+1);
        n=strlen(s+1);
        memset(g,0,(sizeof g[0])*(n+1));
        sam.prepare();
        int l=1,r=n,ans;
        while (l<=r) {
            int mid=(l+r)>>1;
            memset(f,0,(sizeof f[0])*(n+1));
            if (dp(mid)) ans=mid,l=mid+1; else r=mid-1;
        }
        printf("%d\n",ans);
    }
    return 0;
}

转载于:https://www.cnblogs.com/owenyu/p/7148813.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值