CF666E Forensic Examination


题目链接

CF666E Forensic Examination

题目大意

有一个串 \(S\)\(m\) 个串 \(T_i\)\(q\) 次询问,每次给出 \(l,r,p_l,p_r\),求 \(\max_{l\leq i\leq r}\{freq(T_i,S[p_l,p_r])\}\),即 \(S\) 下标在 \([p_l,p_r]\) 的子串在序号 \(\in[l,r]\) 中的 \(T_i\) 的最大出现次数,输出满足最大次数的最小的下标 \(i\) 以及最大出现次数。

\(1\leq|S|,q\leq 5\times10^5\)\(1\leq m,\sum|T_i|\leq 5\times 10^4\)

思路

看到出现次数,果断后缀自动机(连样例里都写了是后缀数据结构),由于是要把 \(S\) 的子串放 \(T_i\) 里面算出现次数,显然把 \(T_i\) 拿出来建 \(SAM\) 会方便一些,有一个常见处理方式,我们将 \(T_i\) 串起来,之间用特殊字符相连构成一个大串 \(T\),然后对 \(T\)\(SAM\)

注意到 \(SAM\) 上节点的右端点(集合)是固定的,而左端点管一段区间,那么这里也将 \(S\) 子串的右端点固定下来,对于每个右端点 \(r\),先预处理出 \(S[0,r]\)\(SAM\) 上的最大匹配长度 \(len_r\) 和对应的状态 \(st_r\) 。首先如果询问的时候 \(len_{p_r} < p_r-p_l+1\),则 \(S[p_l,p_r]\)\(T\) 上找不到对应的匹配,最大次数即为 \(0\)

\(len_{p_r}\geq p_r-p_l+1\) 的时候,我们尝试找到 \(S[p_l,p_r]\) 对应的状态,由于 \(Parent\;Tree\) 到根的树链上的 \(Max(s)\) 是连续减小的,所以一直跳 \(par(st)\),直到 \(Max(par(st)),此时必然有 \(p_r-p_l+1\in[Min(st),Max(st)]\)\(st\) 即为对应的状态,而向上跳的过程可以倍增预处理,做到 \(O(nlogn)\)

现在我们要求的就是在 \(Right(st)\) 里,\([l,r]\) 内哪个 \(T_i\) 对应的位置最多,这个显然可以用线段树维护,我们在 \(SAM\) 每一个节点上开一棵权值线段树,在建好 \(Parent\;Tree\) 后自底向上更新 \(Right\) 信息,每次进行线段树合并即可,预处理 \(O(nlogn)\)

综上,时间复杂度为 \(O((n+q)logn)\)

实现细节

  • 由于每个节点的线段树后来都可能被访问到,所以要采用新建点的方式进行线段树合并,空间复杂度有 \(2\) 倍左右的常数,实际上要开的再大一点,开刚好 \(2\) 倍会 RE。

  • 本人对字符串匹配时的暴力跳 \(fail\) 做了一点优化,尽管实测没啥用,但还是说一下:

    对每个节点维护一个属性 \(frt_\sum\),表示第一个 \(Trans(s,c)\neq null\) 的祖先,这个在 \(dfs\) \(Parent\;Tree\) 的时候自顶向下维护一下即可,这样匹配转移时可以做到严格 \(O(1)\)

  • 有一个非常离谱的卡常,开始读入写的是 \(cin\) 去同步,后来改成了 \(scanf\) 和数字快读,效果图:

(为什么可以快这么多)

Code

#include
#include
#include
#define mem(a,b) memset(a, b, sizeof(a))
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define per(i,b,a) for(int i = (b); i >= (a); i--)
#define N 505000
#define T 50500
#define LOG 14
#define PII pair
#define fr first
#define sc second
#define t(x) SAM.t[x]
using namespace std;

inline int read(){
    int s = 0, w = 1;
    char ch = getchar();
    while(ch < '0' || ch > '9'){ if(ch == '-') w = -1; ch = getchar(); }
    while(ch >= '0' && ch <= '9') s = (s<<3)+(s<<1)+(ch^48), ch = getchar();
    return s*w;
}

struct Segment_Tree{
    struct node{
        int ls, rs;
        PII mx;
        node(){ ls = rs = 0, mx = {0, 0}; }
    } t[4*T*LOG];
    int cnt;
    Segment_Tree(){ cnt = 0; }

    void update(int x){
        t[x].mx = max(t[t[x].ls].mx, t[t[x].rs].mx);
    }

    void insert(int &x, int pos, int l, int r){
        x = ++cnt;
        if(l == r){ t[x].mx = {1, -pos}; return; }
        int mid = (l+r)>>1;
        if(pos <= mid) insert(t[x].ls, pos, l, mid);
        else insert(t[x].rs, pos, mid+1, r);
        update(x);
    }

    inline int merge(int a, int b, int l, int r){
        if(!a || !b) return a+b;
        register int root = ++cnt;
        if(l == r){
            t[root].mx = t[a].mx, t[root].mx.fr += t[b].mx.fr;
        } else{
            int mid = (l+r)>>1;
            t[root].ls = merge(t[a].ls, t[b].ls, l, mid);
            t[root].rs = merge(t[a].rs, t[b].rs, mid+1, r);
            update(root);
        }
        return root;
    }

    inline PII query(int x, int l, int r, int left, int right){
        if(!x) return {-1, 0};
        if(l >= left && r <= right) return t[x].mx;
        register int mid = (l+r)>>1;
        register PII ret = {-1, 0};
        if(mid >= left) ret = max(ret, query(t[x].ls, l, mid, left, right));
        if(mid < right) ret = max(ret, query(t[x].rs, mid+1, r, left, right));
        return ret;
    }
} SGT;

int m;
char s[N], t[T];

struct Suffix_Automaton{
    struct state{
        int len, link, root, par[27];
        int next[27], up[LOG];
    } t[N<<1];
    int cnt, lst;

    void init(){ cnt = lst = 1; }

    void extend(int c, int id){
        int cur = ++cnt;
        t[cur].len = t[lst].len+1;
        if(id) SGT.insert(t[cur].root, id, 1, m);
        int u = lst;
        while(u && !t[u].next[c])
            t[u].next[c] = cur, u = t[u].link;

        if(u == 0) t[cur].link = 1;
        else{
            int q = t[u].next[c];
            if(t[q].len == t[u].len+1) t[cur].link = q;
            else{
                int nq = ++cnt;
                t[nq].len = t[u].len+1;
                memcpy(t[nq].next, t[q].next, sizeof(t[nq].next));
                t[nq].link = t[q].link;
                t[q].link = t[cur].link = nq;

                while(u && t[u].next[c] == q)
                    t[u].next[c] = nq, u = t[u].link;
            }
        }
        lst = cur;
    }
} SAM;

int head[N*2], to[2*N], nxt[2*N];
int cnt;
int state[N], len[N];

void init(){ mem(head, -1), cnt = -1; }
void add_e(int a, int b){
    nxt[++cnt] = head[a], head[a] = cnt, to[cnt] = b;
}

void dfs(int x){
    rep(c,0,26){
        if(!t(x).next[c]) t(x).par[c] = t(t(x).link).par[c];
        else t(x).par[c] = x;
    }
    t(x).up[0] = t(x).link;
    rep(i,1,13) t(x).up[i] = t(t(x).up[i-1]).up[i-1];
    for(int i = head[x]; ~i; i = nxt[i]){
        int y = to[i];
        dfs(y), t(x).root = SGT.merge(t(x).root, t(y).root, 1, m);
    }
}

int main(){
    scanf("%s %d", s, &m);
    SAM.init();
    rep(i,1,m){
        scanf("%s", t);
        int siz = strlen(t);
        rep(j,0,siz-1) SAM.extend(t[j]-'a', i);
        if(i != m) SAM.extend(26, 0);
    }

    init();
    rep(i,1,SAM.cnt) add_e(t(i).link, i);
    dfs(1);

    int st = 1, l = 0, n = strlen(s);
    rep(i,0,n-1){
        int c = s[i]-'a';
        if(t(st).next[c]) l++, st = t(st).next[c];
        else{
            if(t(st).par[c]){
                st = t(st).par[c], l = t(st).len+1;
                st = t(st).next[c];
            } else st = 1, l = 0;
        }
        len[i+1] = l, state[i+1] = st;
    }

    int q, r, pl, pr;
    q = read();
    while(q--){
        l = read(), r = read(), pl = read(), pr = read();
        if(len[pr] < pr-pl+1) printf("%d 0\n", l);
        else{
            st = state[pr];
            per(i,13,0) if(t(t(st).up[i]).len >= pr-pl+1) st = t(st).up[i];
            PII ans = SGT.query(t(st).root, 1, m, l, r);
            if(ans.fr == -1) ans = {0, -l};
            printf("%d %d\n", -ans.sc, ans.fr);
        }
    }
    return 0;
}