Address
Meaning
- 给定一个字符串
S 和
m 个字符串
T1,T2,...,Tm
- 有
q 个询问,每个询问给出四个参数
l 、
r 、
pl 、
pr
- 求
S 的子串
[pl,pr] 在
Tl 、
Tl+1 、…、
Tr 中的哪个串中出现的次数最多
- 如果出现次数最多的有多个串则取编号最小的
- 对于每组询问输出编号和出现次数
-
1≤∣S∣≤5×105 ,
1≤m≤5×104 ,
1≤∑i=1m∣Ti∣≤5×104 ,
1≤q≤5×105
- 时限 6s ,空限 768MB
Solution
- 首先把所有的
T1,T2,...,Tm 和
S 放在一起建立广义 SAM ,下面就对这个 SAM 对应的 Parent 树进行讨论
- 下面我们定义「黑点」为对
Right 集合有贡献的点(即 SAM 构建过程中,不是被拆解出的所有状态点)
- 第一个问题:对于树上的已知节点
u 以及已知区间
[l,r] ,如何知道
T[l...r] 这些串中,哪个串出现状态
u 的次数最多(相同者取最小编号),以及出现次数
- 根据 Parent 树的性质,状态
u 的
Right 集合可以用
u 的子树内所有黑点的某些东西表示出来
- 又根据 SAM 的性质,一个黑点对应原串的一个前缀。相应地,在广义 SAM 上,一个黑点对应原串集合的 Trie 树上根到一个点的路径
- 可以在每个黑点上,用一个
vector
储存这个黑点对应了字符串集合
T 中哪些串的前缀
- 以下把一个黑点上的 vector 内存的
T 内的字符串编号记作
[1,m] 内的某种颜色
- 我们的做法出来了:这个问题就是求
u 的子树内哪种颜色出现次数最多(相同则取最小编号颜色)以及出现次数
- 可以使用线段树合并
或者可持久化线段树解决这个问题
- 对每个点开一棵线段树,下标为颜色,每个节点存出现次数最多的颜色及出现次数,对于每个点
u ,通过线段树合并从
u 的子节点的信息合并到点
u 的信息
- 到现在,我们已经解决了第一个问题
- 第二个问题:已知区间
[pl,pr] ,如何找到子串
S[pl...pr] 在 SAM 上对应的状态点
- 如果是
S[1...pr] 对应的状态点,那么要找的点显然是
S 的长度为
pr 的前缀对应的黑点
- 而根据 Parent 树的性质,
S[pl...pr] 对应的点是
S[1...pr] 对应点的祖先,且每个点的父亲节点的
maxl 都严格小于自己的
maxl
- 于是,如果
S[1...pr] 对应的状态点为
u ,那么我们要做的就是找到
u 的祖先中,离
u 最远的,满足
maxlv≥r−l+1 的点
v
- 可以使用树上倍增找到这个点
v
- 这样我们的做法就出来了:先通过线段树合并预处理 Parent 树每个点的子树内信息,询问时通过树上倍增找到
S[pl...pr] 对应的状态
u ,再查询状态
u 对应的线段树上区间
[l,r] 内的信息
- 复杂度
O((∣S∣+∑i=1m∣Ti∣+q)(logm+log(∣S∣+∑i=1m∣Ti∣)))
Code
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
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;
}
template <class T>
inline T Max(const T &a, const T &b) {return a > b ? a : b;}
const int N = 55e4 + 5, M = 11e5 + 5, L = 1e7 + 5, LogN = 22;
char s[N];
int m, q, trie[N][26], top[N], totTrie, totSam, totTree, ends[N],
ecnt, nxt[M], adj[M], go[M], rt[M], fa[M][LogN];
std::vector<int> col[M];
void add_edge(int u, int v)
{
nxt[++ecnt] = adj[u]; adj[u] = ecnt; go[ecnt] = v;
}
struct SAM
{
int fa, maxl, go[26];
} sam[M];
struct data
{
int col, maxv;
friend inline bool operator > (data a, data b)
{
return a.maxv > b.maxv || (a.maxv == b.maxv && a.col < b.col);
}
};
struct SegTree
{
int lc, rc; data val;
} T[L];
int extend(int c, int lst)
{
int x = lst;
sam[lst = ++totSam].maxl = sam[x].maxl + 1;
for (; x && !sam[x].go[c]; x = sam[x].fa)
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; sam[p = ++totSam] = sam[y];
sam[lst].fa = sam[y].fa = p;
sam[p].maxl = sam[x].maxl + 1;
for (; x && sam[x].go[c] == y; x = sam[x].fa)
sam[x].go[c] = p;
return lst;
}
void ins(int x)
{
int i, n = strlen(s + 1), u = 1;
for (int i = 1; i <= n; i++)
{
int c = s[i] - 'a';
if (!trie[u][c])
top[trie[u][c] = ++totTrie] = extend(c, top[u]);
u = trie[u][c];
if (x) col[top[u]].push_back(x);
else ends[i] = top[u];
}
}
void add(int l, int r, int pos, int &p)
{
if (!p) p = ++totTree;
if (l == r) return (void) (T[p].val.col = l, T[p].val.maxv++);
int mid = l + r >> 1;
if (pos <= mid) add(l, mid, pos, T[p].lc);
else add(mid + 1, r, pos, T[p].rc);
if (T[p].lc && T[p].rc) T[p].val = Max(T[T[p].lc].val, T[T[p].rc].val);
else T[p].val = T[p].lc ? T[T[p].lc].val : T[T[p].rc].val;
}
data query(int l, int r, int s, int e, int p)
{
if (!p) return (data) {s, 0};
if (l == s && r == e) return T[p].val;
int mid = l + r >> 1;
if (e <= mid) return query(l, mid, s, e, T[p].lc);
else if (s >= mid + 1) return query(mid + 1, r, s, e, T[p].rc);
else return Max(query(l, mid, s, mid, T[p].lc),
query(mid + 1, r, mid + 1, e, T[p].rc));
}
int merge_tree(int l, int r, int x, int y)
{
if (!x || !y) return x ^ y;
if (l == r) return T[++totTree].val.maxv = T[x].val.maxv + T[y].val.maxv,
T[totTree].val.col = l, totTree;
int mid = l + r >> 1, p = ++totTree;
T[p].lc = merge_tree(l, mid, T[x].lc, T[y].lc);
T[p].rc = merge_tree(mid + 1, r, T[x].rc, T[y].rc);
if (T[p].lc && T[p].rc) T[p].val = Max(T[T[p].lc].val, T[T[p].rc].val);
else T[p].val = T[p].lc ? T[T[p].lc].val : T[T[p].rc].val;
return p;
}
void dfs(int u)
{
int sz = col[u].size();
for (int i = 0; i < sz; i++)
add(1, m, col[u][i], rt[u]);
for (int i = 0; i < 20; i++)
fa[u][i + 1] = fa[fa[u][i]][i];
for (int e = adj[u], v = go[e]; e; e = nxt[e], v = go[e])
fa[v][0] = u, dfs(v), rt[u] = merge_tree(1, m, rt[u], rt[v]);
}
int get_substr(int l, int r)
{
int u = ends[r];
for (int i = 20; i >= 0; i--)
if (sam[fa[u][i]].maxl >= r - l + 1)
u = fa[u][i];
return u;
}
int main()
{
int pl, pr, l, r;
top[totTrie = totSam = 1] = 1;
scanf("%s", s + 1);
ins(0);
m = read();
for (int i = 1; i <= m; i++)
scanf("%s", s + 1), ins(i);
for (int i = 2; i <= totSam; i++)
add_edge(sam[i].fa, i);
q = read();
dfs(1);
while (q--)
{
l = read(); r = read(); pl = read(); pr = read();
data res = query(1, m, l, r, rt[get_substr(pl, pr)]);
printf("%d %d\n", res.col, res.maxv);
}
return 0;
}