Siyuan

「CTSC 2012」熟悉的文章
「CTSC 2012」熟悉的文章
扫描右侧二维码阅读全文
19
2019/05

「CTSC 2012」熟悉的文章

题目链接:Luogu 4022

阿米巴是小强的好朋友。

在小强眼中,阿米巴是一个作文成绩很高的文艺青年。为了获取考试作文的真谛,小强向阿米巴求教。阿米巴给小强展示了几篇作文,小强觉得这些文章怎么看怎么觉得熟悉,仿佛是某些范文拼拼凑凑而成的。小强不禁向阿米巴投去了疑惑的眼光,却发现阿米巴露出了一个狡黠的微笑。

为了有说服力地向阿米巴展示阿米巴的作文是多么让人觉得眼熟,小强想出了一个评定作文「熟悉程度」的量化指标:$L_0$。小强首先将作文转化成一个 $01$ 串。之后,小强搜集了各路名家的文章,同样分别转化成 $01$ 串后,整理出一个包含了 $m$ 个 $01$ 串的标准作文库。

小强认为:如果一个 $01$ 串长度不少于 $L$ 且在标准作文库中的某个串里出现过(即它是标准作文库 的某个串的一个连续子串),那么它是「熟悉」的。对于一篇作文 $A$,如果能够把 $A$ 分割成若干段子串,其中「熟悉」的子串的长度总和不少于 $A$ 总长度的 $90\%$,那么称 $A$ 是「熟悉的文章」。$L_0$ 是能够让 $A$ 成为「熟悉的文章」的所有 $L$ 的最大值。如果不存在这样的 $L$,那么规定 $L_0 = 0$。

现在小强有 $n$ 篇作文需要进行判断,请你分别求出他们的 $L_0$ 值。

数据范围:输入文件的长度不超过 $1.1 \times 10 ^ 6​$ 字节。


Solution

首先考虑二分 $L$ 的值,求出当前的 $L$ 下的「熟悉」的子串的总长度的最大值。

$$ f(i) = \max_{j = i - s_i} ^ {i - L}f(j) + i - j $$

其中 $s_i$ 表示以 $i$ 结束的字符串在标准作文库中出现的最长长度。可以直接对作文库建立广义 $\text{SAM}​$ 然后预处理出来。

发现 $i - s_i$ 一定是单调不降的,$i - L$ 一定是单调递增的,可以使用单调队列维护 $f(j) - j$,可以优化掉一个 $\mathcal O(n)$ 的复杂度。

时间复杂度:$\mathcal O(n \log n)$。


Code

#include <cstdio>
#include <cstring>
#include <algorithm>

const int N = 1.1e6 + 5;

int n, m, f[N], p[N], q[N];
char s[N];

template <int MAX, int S>
struct SAM {
    static const int N = MAX << 1;
    int tot, lst, len[N], lnk[N], nxt[N][S];
    void clear(int x) {
        std::fill(nxt[x], nxt[x] + S, 0);
        len[x] = lnk[x] = 0;
    }
    void clear() {
        clear(tot = lst = 1);
    }
    void insert(int x) {
        int cur = ++tot;
        clear(cur);
        len[cur] = len[lst] + 1;
        int p = lst;
        for (; p && !nxt[p][x]; p = lnk[p]) nxt[p][x] = cur;
        if (!p) {
            lnk[cur] = 1;
        } else {
            int q = nxt[p][x];
            if (len[q] == len[p] + 1) {
                lnk[cur] = q;
            } else {
                int c = ++tot;
                clear(c);
                len[c] = len[p] + 1;
                lnk[c] = lnk[q];
                std::copy(nxt[q], nxt[q] + S, nxt[c]);
                for (; p && nxt[p][x] == q; p = lnk[p]) nxt[p][x] = c;
                lnk[cur] = lnk[q] = c;
            }
        }
        lst = cur;
    }
};

SAM<N, 2> S;

void match(int n) {
    for (int u = 1, l = 0, i = 1; i <= n; i++) {
        int x = s[i] - '0';
        for (; u && !S.nxt[u][x]; l = S.len[u = S.lnk[u]]);
        if (!u) {
            u = 1, l = 0;
        } else {
            u = S.nxt[u][x], l++;
        }
        p[i] = l;
    }
}
bool check(int x, int n) {
    std::fill(f + 1, f + x, 0);
    int l = 1, r = 0;
    for (int i = x; i <= n; i++) {
        for (; l <= r && f[q[r]] - q[r] <= f[i - x] - (i - x); r--);
        q[++r] = i - x;
        for (; l <= r && q[l] < i - p[i]; l++);
        f[i] = f[i - 1];
        if (l <= r) f[i] = std::max(f[i], f[q[l]] + i - q[l]);
    }
    return f[n] * 10 >= n * 9;
}
int main() {
    scanf("%d%d", &n, &m);
    S.clear();
    for (int i = 1; i <= m; i++) {
        scanf("%s", s + 1);
        int len = strlen(s + 1);
        S.lst = 1;
        for (int j = 1; j <= len; j++) S.insert(s[j] - '0');
    }
    for (int i = 1; i <= n; i++) {
        scanf("%s", s + 1);
        int len = strlen(s + 1);
        match(len);
        int l = 0, r = len, ans = 0;
        while (l <= r) {
            int mid = (l + r) >> 1;
            check(mid, len) ? l = (ans = mid) + 1 : r = mid - 1;
        }
        printf("%d\n", ans);
    }
    return 0;
}

发表评论