【后缀自动机】[BZOJ 2806]Cheat

题目描述:就是给你多个01串当作字典然后给你另一个01串问你这个01串长度90%以上被匹配时的分段匹配的段的最大值 L0 (这个最大值是当前分段每一段的最小值,就是每一段长度都大于这个值,求这个值的最大值)
我看到这道题就知道要把所有的字典的串合成一个串来搞,原来弄过一个AC自动机的题目和这个描述很像,自然而然就这么弄了,建立SAM的时候每一个串之间弄个分隔符。原因不说了。现在我们只要先求出每一个位置向前能够匹配的最大值 f(i) 为什么只求最大值呢?因为每一个位置最大值能够向前匹配,那么小于最大值的串显然是最大值的后缀子串,显然仍然能够匹配,所以只需要求每个位置的能够匹配的最大值就可以了。然后怎么搞呢枚举每一段显然不可能,那这种情况下二分吧,二分当前的 L0 然后怎么检测当前的 L0 是否可用呢,另 dp(i) 为1-i位能够被 L0 匹配的最大数量
首先因为 dp(i)>=dp(i1) 所以我们不用看 if(i) 之前的位置,因为 dp(if(i))>=dp(if(i)k) 然后发现长度起码为 L0 所以我们只需要关注 [if(i),iL0] 这个区间内的 dp 值就行了
所以让 dp(i) 最大那么 dp(i)=max(dp(j)+(ij))if(i)1<j<iL0+1 那么这个算法的复杂度是 O(n2logn) 显然满足不了,那么进行优化 dp(i)=max(dp(j)j)+i 那么 dp(i)i=max(dp(j)j) 那么我们另 g(i)=dp(i)i 求每一个点之前的区间内的最大值,试试看单调队列优化,如果要用单调队列优化需要证明 if(i) 是单调递增的,需要证明 f(i) 是单调递减的那么我们发现如果有一个发现如果前一个串 a2a3...an 显然是 a2a3...anan+1 如果 a1a2a3...anan+1 可以被匹配那么显然 a1a2a3...an 也可以被匹配那么前一个就不是 a2a3...an 那么 f(i+1)<=f(i)+1 那么这个时候区间的左端点要么向右移动要么不动,然后现在用单调队列优化就行了,每次显然是多了一个 g(iL0) 。。。wa了5个小时,发现是忘了清零。。。好了现在复杂度变成 O(nlogn)

#include <cstdio>
#include <cstring>
#include <queue>
#include <algorithm>
using namespace std;
const int MAXN = 2200010;
const int INF = 100000000;
struct node{
    node *p[4], *pre;
    int len;
}Edges[MAXN*2+10], *ecnt=Edges+1,*root=Edges,*last=Edges, *st[MAXN*2+10];
void Insert(int w){
    node *np = ecnt++;
    node *p = last;
    np->len = p->len+1;
    while(p&&!p->p[w])
        p->p[w]=np, p=p->pre;
    if(!p){
        np->pre = root;
    }else{
        node *q = p->p[w];
        if(p->len+1 == q->len){
            np->pre = q;
        }else{
            node *nnd = ecnt++;
            memcpy(nnd->p, q->p, sizeof (nnd->p));
            nnd->len = p->len+1; nnd->pre = q->pre; q->pre = nnd; np->pre = nnd;
            while(p&&p->p[w]==q)
                p->p[w]=nnd, p=p->pre;
        }
    }
    last = np;
}
const int MAXT = 2200010;
char s[MAXT+10];
int dp[MAXT+10], dp2[MAXT+10], que[MAXT];
bool check(int l){
    //dp2[i] = max{dp2[j]+i-j}   i-out  dp2[i]-i = max{dp2[j]-j}//
    int len = strlen(s);
    int lk=0, rk=0, ins;
    for(int i=0;i<l;i++)
        dp2[i] = 0;
    for(int i=l;i<=len;i++){
        dp2[i] = dp2[i-1];
        ins = i-l;
        while(lk<rk&&dp2[ins]-ins>=dp2[que[rk-1]]-que[rk-1]) rk--;
        que[rk++] = ins;
        while(lk<rk&&que[lk]<i-dp[i]) lk++;
        if(lk < rk)
            dp2[i] = max(dp2[i], dp2[que[lk]] - que[lk] + i);
    }
    return dp2[len] * 10 >= len * 9;
}
int main(){
    int n, m;
    scanf("%d %d", &n, &m);
    for(int i=0;i<m;i++){
        scanf("%s", s);
        int len = strlen(s);
        for(int j=0;j<len;j++)
            Insert(s[j]-'0');
        Insert(2);
    }
    for(int i=0;i<n;i++){
        node *now = root;
        scanf("%s", s);
        int L = 0, len = strlen(s);
        for(int j=0;j<len;j++){
            int idx = s[j]-'0';
            if(now->p[idx]){
                now = now->p[idx];
                L++;
            }else{
                while(now && !now->p[idx])
                    now = now->pre;
                if(!now){
                    now = root;
                    L = 0;
                }else{
                    L = now->len + 1;
                    now = now->p[idx];
                }
            }
            dp[j+1] = L;
        }
        int l=1, r=len, ans=0;
        while(l <= r){
            int mid = (l + r) >> 1;
            if(check(mid)){ l = mid+1; ans = mid;
            }else r = mid-1;
        }
        printf("%d\n", ans);
    }

    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值