BZOJ2806 [Ctsc2012]Cheat 【后缀自动机 + 二分 + 单调队列优化DP】

题目

1318028-20180118140826131-514469424.jpg
1318028-20180118140831412-1362367825.jpg

输入格式

第一行两个整数N,M表示待检查的作文数量,和小强的标准作文库
的行数
接下来M行的01串,表示标准作文库
接下来N行的01串,表示N篇作文

输出格式

N行,每行一个整数,表示这篇作文的Lo 值。

输入样例

1 2

10110

000001110

1011001100

输出样例

4

提示

输入文件不超过1100000字节

注意:题目有改动,可识别的长度不小于90%即可,而不是大于90%

题解

想来练练SAM,却跪在了单调队列DP上。。。QAQ

根据后缀数组进行多串匹配时,用一个未出现的字符将各串连接起来形成一个串
我们可以照搬到后缀自动机来,对m个模板串建立后缀自动机

此时我们可以在后缀自动机上走一遍求出匹配串每个位置为结尾所能匹配的最大长度【见代码init】
记为len[i],即表示匹配的子串\([i - len[i] + 1,i]\)可以与模板串匹配

有了这些,我们如何计算\(L_0\)
仔细思考发现L具有单调性,即在长度至少为L时选择的最小切割方案下,对长度小于L的限制仍然满足,长度小于L的可以照搬L的切割方法而满足条件

这个时候我们就可以二分了
对于长度L,我们可以用DP检验,令f[i]表示前i个字符在L限制下能匹配的最大长度
首先f[i]至少等于f[i - 1],因为i可以单独被化为出来而转移到f[i - 1]【也就是说f[i]单调不下降,这个待会会用到】
我们有状态转移方程:\(f[i] = max{f[j] + i - j},j\in [i - len[i],i - L]\)

解释:由于L的限制,要切割出以i结尾产生贡献的区间,必须在i - L及之前,而i最多匹配到i - len[i] + 1,所以\(j\in [i - len[i],i - L]\)
最后的匹配长度就是i割出这段的长度 + f[j]

这样做是\(O(n^2logn)\)的,不能满足题意
考虑优化
我们将方程变形\(f[i] - i = f[j] - j\),左边是只与i有关的量,与决策无关
右边是只与j有关的量,由f[j]的定义,f[j] - j表示前j个没匹配的个数【负数】,由于没匹配的个数不会变少,所以f[j] - j单调递减

也就是说决策具有单调性,且只与j有关,可以采用单调队列优化
【一定要好好写队列QAQ,WA了几发】

呼啦啦~~~搞完了【CTSC的题目哇。】

#include<iostream>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long int
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define BUG(s,n) for (int i = 1; i <= (n); i++) cout<<s[i]<<' '; puts("");
using namespace std;
const int maxn = 3000005,maxm = 100005,INF = 1000000000;
int pre[maxn],ch[maxn][3],step[maxn],cnt,last;
char s[maxn];
void ins(int x){
    int p = last,np = ++cnt;
    last = np; step[np] = step[p] + 1;
    while (p && !ch[p][x]) ch[p][x] = np,p = pre[p];
    if (!p) pre[np] = 1;
    else {
        int q = ch[p][x];
        if (step[q] == step[p] + 1) pre[np] = q;
        else {
            int nq = ++cnt; step[nq] = step[p] + 1;
            memcpy(ch[nq],ch[q],sizeof(ch[q]));
            pre[nq] = pre[q]; pre[q] = pre[np] = nq;
            while (ch[p][x] == q) ch[p][x] = nq,p = pre[p];
        }
    }
}
int q[maxn],head,tail,f[maxn],N,len[maxn];
bool check(int L){
    head = tail = f[0] = 0;
    for (int i = 1; i <= N; i++){
        f[i] = f[i - 1];
        int l = i - len[i],r = i - L;
        if (r >= 0){
            while (head < tail && f[q[tail - 1]] - q[tail - 1] < f[r] - r) tail--;
            q[tail++] = r;
        }
        while (head < tail && q[head] < l) head++;
        if (head < tail) f[i] = max(f[i],f[q[head]] - q[head] + i);
    }
    return 10 * f[N] >= 9 * N;
}
void init(){
    int u = 1,id,ans = 0;
    for (int i = 1; i <= N; i++){
        id = s[i] - '0';
        if (ch[u][id]) len[i] = ++ans,u = ch[u][id];
        else {
            while (u != 1 && !ch[u][id]) u = pre[u];
            if (u == 1) len[i] = ans = 0;
            else len[i] = ans = step[u] + 1,u = ch[u][id];
        }
    }
}
int main(){
    int n,m; last = cnt = 1;
    scanf("%d%d",&n,&m);
    while (m--){
        scanf("%s",s); N = strlen(s);
        for (int i = 0; i < N; i++) ins(s[i] - '0');
        ins(2);
    }
    while (n--){
        scanf("%s",s + 1); N = strlen(s + 1);
        init();
        int l = 0,r = N,mid,ans = 0;
        while (l <= r){
            mid = l + r >> 1;
            if (check(mid)) l = mid + 1,ans = mid;
            else r = mid - 1;
        }
        printf("%d\n",ans);
    }
    return 0;
}

转载于:https://www.cnblogs.com/Mychael/p/8309918.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值