题解 CF666E Forensic Examination
题意
给定一个长度为 $n$ 的字符串 $s$ 和 $m$ 个字符串 $t_{1\dots m}$,$q$ 次询问,每次查询 $s\left[p_l:p_r\right]$ 在 $t_{l\dots r}$ 中最多的出现次数。
数据范围:$1\le n,q,\sum |t|\le5\times10^5$,$1\le m\le5\times10^4$
题解
对 $m$ 个串建一个广义 SAM,对 SAM 中的每个节点建一棵下标为所属串的动态开点权值线段树,记录区间最大值与其对应下标 。
然后建立 parent tree,在上面跑一个线段树合并,合并的方式只能是新建节点,直接在原树上合并会改变其他子节点的结构。
类似求 LCS,处理出 $\forall i\in\left[1,n\right],s\left[1:i\right]$ 在 SAM 匹配的最长后缀的长度 $maxl_i$ 及对应节点 $pos_i$。
每次查询,先判没有匹配的情况,即 $r-l+1>maxl_{p_r}$,然后从 $pos_{p_r}$ 开始在 parent tree 上 倍增,找到 $\min len_u$ 满足 $len_u\ge p_l-p_r+1$,此时点 $u$ 恰好包含了 $s\left[p_l:p_r\right]$ 这一状态,直接查询点 $u$ 的权值线段树中 $\left[l,r\right]$ 中的最大值即可。
认为 $n,q,\sum|t|$ 同级,则时间复杂度为 $O(n\log n)$。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10, Log = 20, S = 26;
struct node {
int mx, cnt;
node(int Mx = 0, int Cnt = 0) { mx = Mx, cnt = Cnt; }
friend bool operator < (const node &qwq, const node &awa) {
return qwq.cnt == awa.cnt ? qwq.mx > awa.mx : qwq.cnt < awa.cnt;
}
friend node operator + (const node &qwq, const node &awa) {
return node(qwq.mx, qwq.cnt + awa.cnt);
}
};
struct edge {
int to, nxt;
} e[N << 1];
int head[N], ecnt;
void add(int u, int v) {
e[++ecnt] = (edge){v, head[u]};
head[u] = ecnt;
}
int n, m, q;
int fa[Log][N], dep[N], pos[N], maxl[N];
char s[N], t[N];
namespace SegmentTree {
int rt[N], ls[N * Log], rs[N * Log], tot;
node v[N * Log];
void pushup(int x) { v[x] = max(v[ls[x]], v[rs[x]]); }
void insert(int &x, int l, int r, int p) {
if(!x) x = ++tot;
if(l == r) {
v[x].cnt++;
v[x].mx = l;
return;
}
int mid = l + r >> 1;
if(p <= mid) insert(ls[x], l, mid, p);
else insert(rs[x], mid + 1, r, p);
pushup(x);
}
int merge(int a, int b, int l, int r) {
if(!a || !b) return a + b;
int x = ++tot;
if(l == r) {
v[x] = v[a] + v[b];
return x;
}
int mid = l + r >> 1;
ls[x] = merge(ls[a], ls[b], l, mid);
rs[x] = merge(rs[a], rs[b], mid + 1, r);
pushup(x);
return x;
}
node query(int x, int l, int r, int L, int R) {
if(!x) return node();
if(L <= l && r <= R) return v[x];
node res;
int mid = l + r >> 1;
if(mid >= L) res = max(res, query(ls[x], l, mid, L, R));
if(mid < R) res = max(res, query(rs[x], mid + 1, r, L, R));
return res;
}
} using namespace SegmentTree;
namespace SAM {
struct state {
int len, link, ch[S];
} st[N];
int last, cnt = 1;
void insert(int c, int i) {
int p = last, q, x = ++cnt, y;
st[x].len = st[p].len + 1;
SegmentTree::insert(rt[x], 1, m, i);
for(; p && !st[p].ch[c]; p = st[p].link) st[p].ch[c] = x;
if(!p) st[x].link = 1;
else {
q = st[p].ch[c];
if(st[q].len == st[p].len + 1) st[x].link = q;
else {
st[y = ++cnt] = st[q];
st[y].len = st[p].len + 1;
st[q].link = st[x].link = y;
for(; p && st[p].ch[c] == q; p = st[p].link) st[p].ch[c] = y;
}
}
last = x;
}
void insert(char *t, int k) {
int len = strlen(t + 1);
for(int i = 1; i <= len; i++)
insert(t[i] - 'a', k);
}
} using namespace SAM;
void dfs(int u) {
for(int i = head[u], v; i; i = e[i].nxt) {
v = e[i].to;
fa[0][v] = u;
dfs(v);
rt[u] = merge(rt[u], rt[v], 1, m);
}
}
int main() {
scanf("%s%d", s + 1, &m);
n = strlen(s + 1);
for(int i = 1; i <= m; i++) {
scanf("%s", t + 1);
last = 1, insert(t, i);
}
for(int i = 2; i <= cnt; i++)
add(st[i].link, i);
dfs(1);
for(int i = 1; i < Log; i++)
for(int j = 1; j <= cnt; j++)
fa[i][j] = fa[i - 1][fa[i - 1][j]];
for(int i = 1, c, p = 1, l = 0; i <= n; i++) {
c = s[i] - 'a';
if(st[p].ch[c]) p = st[p].ch[c], l++;
else {
for(; p && !st[p].ch[c]; p = st[p].link);
if(!p) p = 1, l = 0;
else l = st[p].len + 1, p = st[p].ch[c];
}
pos[i] = p, maxl[i] = l;
}
scanf("%d", &q);
while(q--) {
int l, r, pl, pr, len;
scanf("%d%d%d%d", &l, &r, &pl, &pr), len = pr - pl + 1;
if(len > maxl[pr]) { printf("%d 0\n", l); continue; }
int u = pos[pr];
for(int i = Log - 1; ~i; i--)
if(st[fa[i][u]].len >= len)
u = fa[i][u];
node ans = query(rt[u], 1, m, l, r);
if(!ans.cnt) ans.mx = l;
printf("%d %d\n", ans.mx, ans.cnt);
}
return 0;
}

浙公网安备 33010602011771号