给定一个字符串
S
S
S 和
m
m
m 个字符串
T
1
,
T
2
,
.
.
.
,
T
m
T_1,T_2,...,T_m
T1,T2,...,Tm
有
q
q
q 个询问,每个询问给出四个参数
l
l
l 、
r
r
r 、
p
l
p_l
pl 、
p
r
p_r
pr
求
S
S
S 的子串
[
p
l
,
p
r
]
[p_l,p_r]
[pl,pr] 在
T
l
T_l
Tl 、
T
l
+
1
T_{l+1}
Tl+1 、…、
T
r
T_r
Tr 中的哪个串中出现的次数最多
如果出现次数最多的有多个串则取编号最小的
对于每组询问输出编号和出现次数
1
≤
∣
S
∣
≤
5
×
1
0
5
1\le|S|\le5\times10^5
1≤∣S∣≤5×105 ,
1
≤
m
≤
5
×
1
0
4
1\le m\le5\times10^4
1≤m≤5×104 ,
1
≤
∑
i
=
1
m
∣
T
i
∣
≤
5
×
1
0
4
1\le\sum_{i=1}^m|T_i|\le5\times10^4
1≤∑i=1m∣Ti∣≤5×104 ,
1
≤
q
≤
5
×
1
0
5
1\le q\le5\times10^5
1≤q≤5×105
时限 6s ,空限 768MB
Solution
首先把所有的
T
1
,
T
2
,
.
.
.
,
T
m
T_1,T_2,...,T_m
T1,T2,...,Tm 和
S
S
S 放在一起建立广义 SAM ,下面就对这个 SAM 对应的 Parent 树进行讨论
下面我们定义「黑点」为对
R
i
g
h
t
Right
Right 集合有贡献的点(即 SAM 构建过程中,不是被拆解出的所有状态点)
第一个问题:对于树上的已知节点
u
u
u 以及已知区间
[
l
,
r
]
[l,r]
[l,r] ,如何知道
T
[
l
.
.
.
r
]
T[l...r]
T[l...r] 这些串中,哪个串出现状态
u
u
u 的次数最多(相同者取最小编号),以及出现次数
根据 Parent 树的性质,状态
u
u
u 的
R
i
g
h
t
Right
Right 集合可以用
u
u
u 的子树内所有黑点的某些东西表示出来
又根据 SAM 的性质,一个黑点对应原串的一个前缀。相应地,在广义 SAM 上,一个黑点对应原串集合的 Trie 树上根到一个点的路径
可以在每个黑点上,用一个vector储存这个黑点对应了字符串集合
T
T
T 中哪些串的前缀
以下把一个黑点上的 vector 内存的
T
T
T 内的字符串编号记作
[
1
,
m
]
[1,m]
[1,m] 内的某种颜色
我们的做法出来了:这个问题就是求
u
u
u 的子树内哪种颜色出现次数最多(相同则取最小编号颜色)以及出现次数
可以使用线段树合并或者可持久化线段树解决这个问题
对每个点开一棵线段树,下标为颜色,每个节点存出现次数最多的颜色及出现次数,对于每个点
u
u
u ,通过线段树合并从
u
u
u 的子节点的信息合并到点
u
u
u 的信息
到现在,我们已经解决了第一个问题
第二个问题:已知区间
[
p
l
,
p
r
]
[p_l,p_r]
[pl,pr] ,如何找到子串
S
[
p
l
.
.
.
p
r
]
S[p_l...p_r]
S[pl...pr] 在 SAM 上对应的状态点
如果是
S
[
1...
p
r
]
S[1...p_r]
S[1...pr] 对应的状态点,那么要找的点显然是
S
S
S 的长度为
p
r
p_r
pr 的前缀对应的黑点
而根据 Parent 树的性质,
S
[
p
l
.
.
.
p
r
]
S[p_l...p_r]
S[pl...pr] 对应的点是
S
[
1...
p
r
]
S[1...p_r]
S[1...pr] 对应点的祖先,且每个点的父亲节点的
m
a
x
l
maxl
maxl 都严格小于自己的
m
a
x
l
maxl
maxl
于是,如果
S
[
1...
p
r
]
S[1...p_r]
S[1...pr] 对应的状态点为
u
u
u ,那么我们要做的就是找到
u
u
u 的祖先中,离
u
u
u 最远的,满足
m
a
x
l
v
≥
r
−
l
+
1
maxl_v\ge r-l+1
maxlv≥r−l+1 的点
v
v
v
可以使用树上倍增找到这个点
v
v
v
这样我们的做法就出来了:先通过线段树合并预处理 Parent 树每个点的子树内信息,询问时通过树上倍增找到
S
[
p
l
.
.
.
p
r
]
S[p_l...p_r]
S[pl...pr] 对应的状态
u
u
u ,再查询状态
u
u
u 对应的线段树上区间
[
l
,
r
]
[l,r]
[l,r] 内的信息
复杂度
O
(
(
∣
S
∣
+
∑
i
=
1
m
∣
T
i
∣
+
q
)
(
log
m
+
log
(
∣
S
∣
+
∑
i
=
1
m
∣
T
i
∣
)
)
)
O((|S|+\sum_{i=1}^m|T_i|+q)(\log m+\log(|S|+\sum_{i=1}^m|T_i|)))
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>inlineintread(){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<classT>inline T Max(const T &a,const T &b){return a > b ? a : b;}constint 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];voidadd_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;friendinlinebooloperator>(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];intextend(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;}voidins(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];}}voidadd(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);elseadd(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)returnquery(l, mid, s, e, T[p].lc);elseif(s >= mid +1)returnquery(mid +1, r, s, e, T[p].rc);elsereturnMax(query(l, mid, s, mid, T[p].lc),query(mid +1, r, mid +1, e, T[p].rc));}intmerge_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;}voiddfs(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]);}intget_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;}intmain(){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);}return0;}