[BZOJ2806][Ctsc2012]Cheat(广义后缀自动机+二分答案+dp+单调队列)

Address

https://www.lydsy.com/JudgeOnline/problem.php?id=2806

Solution

Q:如何判断一个串是否是标准串之一的子串?
A:将标准串建成一棵 Trie ,然后在 Trie 上建立后缀自动机,即广义后缀自动机。从初态开始, for 输入串的每一个字符,每次都向对应的字符边转移,如果能转移到则输入串是标准串之一的子串。
回到原问题,先把标准串建成广义后缀自动机。
然后对于每个询问,二分答案进行 DP :
设当前判定的答案为 m i d
f [ i ] 表示到串的长度为 i 的前缀的所有划分方法中,熟悉的子串的长度总和的最大值。
可以用后缀自动机计算出以 i 为结尾,在标准串中作为子串出现过的最长子串长度 l e n [ i ]
计算方法:从 l e n [ i 1 ] 转移。 l e n [ 0 ] = 0 在计算 len 的过程中,维护子串 [i-len+1,i] 在 SAM 中代表的状态,从 l e n [ i 1 ] 转移到 l e n [ i ] 时,不断地跳 Parent ,直到当前状态能通过文章串第 i 个字符转移为止,这时候通过文章串第 i 个字符转移,得到 l e n [ i ]
所以得出朴素的转移:

f [ i ] = max j = i l e n [ i ] i m i d { f [ j ] + i j }

从这个过程容易发现: i l e n [ i ] i 单调不降。
所以转化一下:
f [ i ] = i + max j = i l e n [ i ] i m i d { f [ j ] j }

用单调队列维护 f [ j ] j 的最大值,就可以 O ( 1 ) 转移。

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define For(i, a, b) for (i = a; i <= b; i++)
#define Sam(i, orz) for (; i && orz; i = SAM[i].fa)
using namespace std;
inline int read() {
    int res = 0; bool bo = 0; char c;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') bo = 1; else res = c - 48;
    while ((c = getchar()) >= '0' && c <= '9')
        res = (res << 3) + (res << 1) + (c - 48);
    return bo ? ~res + 1 : res;
}
const int N = 11e5 + 5, M = N << 1;
int n, m, QAQ, QWQ, len, f[N], Q[N], H, T;
char s[N];
struct nodeTrie {
    int go[2];
    void init() {
        memset(go, 0, sizeof(go));
    }
} Trie[N];
struct nodeSAM {
    int fa, maxl, go[2];
    void init() {
        memset(go, 0, sizeof(go));
    }
} SAM[M];
void insTrie(int n, char *s) {
    int i, u = 1;
    For (i, 1, n) {
        int c = s[i] - '0';
        if (!Trie[u].go[c]) Trie[Trie[u].go[c] = ++QAQ].init();
        u = Trie[u].go[c];
    }
}
int insSAM(int c, int lst) {
    int x = lst; SAM[lst = ++QWQ].init();
    SAM[lst].maxl = SAM[x].maxl + 1;
    Sam(x, !SAM[x].go[c]) SAM[x].go[c] = lst;
    if (!x) return SAM[lst].fa = 1, lst;
    int y = SAM[x].go[c];
    if (SAM[x].maxl + 1 == SAM[y].maxl) return SAM[lst].fa = y, lst;
    int p = ++QWQ; SAM[p] = SAM[y];
    SAM[y].fa = SAM[lst].fa = p; SAM[p].maxl = SAM[x].maxl + 1;
    Sam(x, SAM[x].go[c] == y) SAM[x].go[c] = p;
    return lst;
}
void dfs(int u, int lst) {
    int c; For (c, 0, 1) if (Trie[u].go[c])
        dfs(Trie[u].go[c], insSAM(c, lst));
}
bool check(int mid) {
    int i, u = 1, l = 0; H = T = 0;
    For (i, 1, len) {
        int c = s[i] - '0'; f[i] = f[i - 1];
        while (u > 1 && !SAM[u].go[c])
            u = SAM[u].fa, l = SAM[u].maxl;
        if (SAM[u].go[c]) u = SAM[u].go[c], l++;
        int le = i - l, ri = i - mid;
        if (ri < 0) continue;
        while (H < T && f[Q[T]] - Q[T] < f[ri] - ri) T--;
        Q[++T] = ri;
        while (H < T && Q[H + 1] < le) H++;
        if (H < T) f[i] = max(f[i], f[Q[H + 1]] - Q[H + 1] + i);
    }
    return 10 * f[len] >= 9 * len;
}
int main() {
    Trie[QAQ = 1].init(); SAM[QWQ = 1].init();
    cin >> n >> m;
    while (m--) scanf("%s", s + 1), insTrie(strlen(s + 1), s);
    int i; dfs(1, 1);
    while (n--) {
        scanf("%s", s + 1); len = strlen(s + 1);
        int l = 1, r = len;
        while (l <= r) {
            int mid = l + r >> 1;
            if (check(mid)) l = mid + 1;
            else r = mid - 1;
        }
        printf("%d\n", r);
    }
    return 0;
}

猜你喜欢

转载自blog.csdn.net/xyz32768/article/details/81167314
今日推荐