@bzoj - 2806@ [Ctsc2012]Cheat


@description@

给定 M 个 01 串表示文本库,再给 N 个询问。
我们称一个子串是 “L - 熟悉” 的,当且仅当这个子串的长度大于等于 L 且是文本库中某一个串的子串。
每次询问给出一个 01 串 A,如果可以把这个串分成若干段子串,其中 “L0 - 熟悉” 的子串长度和 >= 90%*|A|,则称 L0 满足要求。输出满足要求的 L0 最大值。

input
第一行两个整数 N,M。含义如上。
接下来 M 行的 01 串表示文本库。
接下来 N 行的 01 串表示询问。

output
对于每个询问,输出相应的答案。

sample input
1 2
10110
000001110
1011001100
sample output
4
sample explain
|10110|0110|0|

@solution@

显然二分答案 + dp 判定。
二分出 L,得到 dp[i] = max(dp[i-1], dp[j]+(i-j)),其中 j <= i-L 且 j+1...i 这一段是文本库某一个串的子串。
这是一个很显然的单调队列可以优化的 dp。

因此,我们需要求 A 的每一个前缀的某个长度最长的后缀,使这个后缀是文本串的某一个串的子串。
假设文本库里面只有一个串,就会让人想起后缀自动机寻找两个串的公共子串的过程。也是对于每一个前缀求它长度最长的后缀。
对于多个串,我们需要广义后缀自动机。

广义后缀自动机,概念上就是对多个串建同一个后缀自动机,实现上就是如果要加入新的字符串,就将 last 指针重新移回 root。
如果某个转移边以前出现过会怎样?你会发现新加入的点并不会与其他点连通,因为它不会和它之前的任何结点连边。

@accepted code@

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 1100000;
struct sam{
    sam *ch[2], *fa; int mx;
}pl[2*MAXN + 5], *tcnt, *root, *lst;
void init() {
    tcnt = root = lst = &pl[0];
}
sam *newnode() {
    return (++tcnt);
}
void sam_extend(int x) {
    sam *cur = newnode(), *p = lst;
    cur->mx = lst->mx + 1, lst = cur;
    while( p && !p->ch[x] )
        p->ch[x] = cur, p = p->fa;
    if( !p )
        cur->fa = root;
    else {
        sam *q = p->ch[x];
        if( q->mx == p->mx + 1 )
            cur->fa = q;
        else {
            sam *cne = newnode();
            (*cne) = (*q), cne->mx = p->mx + 1;
            q->fa = cur->fa = cne;
            while( p && p->ch[x] == q )
                p->ch[x] = cne, p = p->fa;
        }
    }
}
char s[MAXN + 5];
int f[MAXN + 5], dp[MAXN + 5], len;
int que[MAXN + 5];
bool check(int L) {
    for(int i=1;i<L;i++)
        dp[i] = 0;
    int s = 1, t = 0;
    for(int i=L;i<=len;i++) {
        while( s <= t && dp[i - L] - (i - L) > dp[que[t]] - que[t] )
            t--;
        que[++t] = i - L;
        while( s <= t && que[s] < i - f[i-1] )
            s++;
        dp[i] = dp[i-1];
        if( s <= t ) dp[i] = max(dp[i], dp[que[s]] + i - que[s]);
    }
    return 10*dp[len] >= 9*len;
}
int main() {
    init();
    int N, M; scanf("%d%d", &N, &M);
    for(int i=1;i<=M;i++) {
        scanf("%s", s); lst = root;
        int len = strlen(s);
        for(int j=0;j<len;j++)
            sam_extend(s[j] - '0');
    }
    for(int i=1;i<=N;i++) {
        scanf("%s", s); len = strlen(s);
        int le = 0, ri = len, res = 0; sam *nw = root;
        for(int j=0;j<len;j++) {
            while( nw && !nw->ch[s[j] - '0'] )
                nw = nw->fa;
            if( !nw ) res = 0, nw = root;
            else res = min(res, nw->mx) + 1, nw = nw->ch[s[j] - '0'];
            f[j] = res;
        }
        while( le < ri ) {
            int mid = (le + ri + 1) >> 1;
            if( check(mid) ) le = mid;
            else ri = mid - 1;
        }
        printf("%d\n", le);
    }
}

@details@

本题好像听他们说,如果你乘 0.9 就会产生浮点误差,然后就会挂掉。
有这么卡精度的吗?才 0.9 啊喂。

posted @ 2019-01-11 14:39  Tiw_Air_OAO  阅读(171)  评论(0编辑  收藏  举报