1. 程式人生 > >[CTSC2012]熟悉的文章 (後綴自動機 單調隊列)

[CTSC2012]熟悉的文章 (後綴自動機 單調隊列)

fin log cstring ostream pre ont 答案 ring mes

/*
首先答案顯然是具有單調性的, 所以可以二分進行判斷

然後當我們二分過後考慮dp來求最長匹配個數, 發現每個點能夠轉移的地點 肯定是一段區間, 然後這樣就能夠得到一個log^2算法
至於每個點的匹配最長區間, 我們可以預處理出所有地點的最長匹配串

然後發現這個東西可以進行單調棧優化, 原因是往後能往前最大匹配到的點是不會超過前面的, 否則前面那個也會更長

然後就能快樂地一個log了


*/
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<iostream>
#define ll long long
#define M 2800010
#define mmp make_pair
using namespace std;
int read() {
    int nm = 0, f = 1;
    char c = getchar();
    for(; !isdigit(c); c = getchar()) if(c == '-') f = -1;
    for(; isdigit(c); c = getchar()) nm = nm * 10 + c - '0';
    return nm * f;
}
char s[M];
int ch[M][2], cnt = 1, lst = 1, fa[M], len[M], n, m;

void insert(int c) {
    int p = ++cnt, f = lst;
    lst = p;
    len[p] = len[f] + 1;
    while(f && !ch[f][c]) ch[f][c] = p, f = fa[f];
    if(f == 0) {
        fa[p] = 1;
    } else {
        int q = ch[f][c];
        if(len[q] == len[f] + 1) {
            fa[p] = q;
        } else {
            int nq = ++cnt;
            memcpy(ch[nq], ch[q], sizeof(ch[q]));
            fa[nq] = fa[q];
            len[nq] = len[f] + 1;
            fa[p] = fa[q] = nq;
            while(f && ch[f][c] == q) ch[f][c] = nq, f = fa[f];
        }
    }
}
int q[M], h, t, pre[M], f[M];
bool check(int k) {
    int l = strlen(s + 1);
    h = 1, t = 0;
    for(int i = 1; i <= l; i++) {
        f[i] = f[i - 1];
        if(i < k) continue;
        while(h <= t && f[q[t]] - q[t] <= f[i - k] - i + k) t--;
        q[++t] = i - k;
        while(h <= t && q[h] < i - pre[i]) h++;
        if(h <= t) f[i] = max(f[i], f[q[h]] + i - q[h]);
    }
    return f[l] * 10 >= l * 9;
}

void getpre() {
    int up = strlen(s + 1), now =  1, lenn = 0;
    for(int i = 1; i <= up; i++) {
        int c = s[i] - '0';

        while(now && !ch[now][c]) now = fa[now], lenn = len[now];
        if(!now) now = 1, lenn = 0;
        else now = ch[now][c], lenn++;

        pre[i] = lenn;
    }
}

int work() {
    getpre();
    int l = 1, r = strlen(s + 1);
    while(l + 1 < r) {
        int mid = (l + r) >> 1;
        if(check(mid)) l = mid;
        else r = mid;
    }
    if(!check(l)) return -1;
    if(check(r)) return r;
    return l;
}

int main() {
    n = read(), m = read();
    for(int i = 1; i <= m; i++) {
        lst = 1;
        scanf("%s", s + 1);
        int l = strlen(s + 1);
        for(int j = 1; j <= l; j++) insert(s[j] - '0');
    }
    while(n--) {
        scanf("%s", s + 1);
        cout << work() << "\n";
    }
    return 0;
}

[CTSC2012]熟悉的文章 (後綴自動機 單調隊列)