Codeforces 666E Forensic Examination (后缀自动机 + 线段树合并)
题解:首先广义 \(SAM\) , 然后分析,每次查询需要查的字串是确定,并且给了你右端点,所以我们在插入文本串 (一开始给的字符串) 时,记录一下第 \(i\) 个字符插入时所对应的节点,然后我们根据后缀链接往上爬,直到爬到一个节点 \(p\) 满足的 \(minLen[p] \leq p_r - p_l + 1 \leq maxLen[p]\) ,这个节点就包含了我们要查的字串,这个过程可以用倍增实现,复杂度为 \(log(n)\) ,此外,我们还要知道每个节点中每个模式串的贡献,所以我们对每个节点建立一颗线段树,记录每个模式串有多少个属于该节点的 \(endpoint\) ,每个节点包含的某模式串的 \(endpoint\) 个数可以 \(dfs\) 一遍 \(link\) 树得出来,但是这样不仅是空间还是时间都不够用,所以考虑线段树合并。具体看代码。
#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
typedef long long LL;
typedef pair<int, int> pii;
const int MAXE = 2e6 + 50;
int n, m;
struct Edge
{
int to, next;
} edge[MAXE * 2];
int k, head[MAXE];
void add(int a, int b){
edge[k].to = b;
edge[k].next = head[a];
head[a] = k++;
}
struct segementTree
{
static const int maxn = 3e6 + 60;
int tree[maxn << 2], ls[maxn << 2], rs[maxn << 2], val[maxn << 2];
int root[maxn << 2];
int sz = 0;
void init() {sz = 0;};
void PushUp(int rt){ // 向上合并
if(tree[ls[rt]] == tree[rs[rt]]){
tree[rt] = tree[ls[rt]];
val[rt] = min(val[ls[rt]], val[rs[rt]]);
} else if(tree[ls[rt]] > tree[rs[rt]]){
tree[rt] = tree[ls[rt]];
val[rt] = val[ls[rt]];
} else {
tree[rt] = tree[rs[rt]];
val[rt] = val[rs[rt]];
}
}
void insert(int le, int ri, int pos, int &rt){ // 模式串pos在该节点的贡献 + 1
if(!rt) rt = ++sz;
if(le == ri) {
tree[rt]++;
val[rt] = le;
return;
}
int mid = (le + ri) >> 1;
if(pos <= mid) insert(le, mid, pos, ls[rt]);
else insert(mid + 1, ri, pos, rs[rt]);
PushUp(rt);
}
int merge(int le, int ri, int u, int v){ // 线段树合并, 这里选择新开一个节点,而不是覆盖,因为空间允许,不需要将查询离线
if(!u || !v) return u | v;
int now = ++sz;
if(le == ri){
tree[now] = tree[u] + tree[v];
val[now] = le;
return now;
}
int mid = (le + ri) >> 1;
ls[now] = merge(le, mid, ls[u], ls[v]);
rs[now] = merge(mid + 1, ri, rs[u], rs[v]);
PushUp(now);
return now;
}
void dfs(int u, int pre){
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to;
if(to == pre) continue;
dfs(to, u);
root[u] = merge(1, m, root[u], root[to]);
}
}
pii Query(int le, int ri, int L, int R, int rt){
if(L <= le && ri <= R){
return pii{tree[rt], val[rt]};
}
int mid = (le + ri) >> 1;
pii ans = {0, 0};
if(L <= mid) {
pii res = Query(le, mid, L, R, ls[rt]);
if(ans.fi < res.fi) ans = res;
}
if(R > mid){
pii res = Query(mid + 1, ri, L, R, rs[rt]);
if(ans.fi < res.fi) ans = res;
}
return ans;
}
} seTree;
struct ex_SAM
{
static const int maxn = 2e6 + 60;
int nex[maxn][26], len[maxn], link[maxn], pos[maxn];
int tot, char_num = 26, custr = 0;
void init() {tot = 1, link[0] = -1;}
int insert_SAM(int last, int c){
int cur = nex[last][c], p = link[last];
len[cur] = len[last] + 1;
while(p != -1){
if(!nex[p][c]) nex[p][c] = cur;
else break;
p = link[p];
}
if(p == -1) {
link[cur] = 0; return cur;
}
int q = nex[p][c];
if(len[p] + 1 == len[q]) {link[cur] = q; return cur;}
int clone = tot++;
for(int i = 0; i < char_num; i++){
nex[clone][i] = len[nex[q][i]] != 0 ? nex[q][i] : 0;
}
len[clone] = len[p] + 1;
while(p != -1 && nex[p][c] == q){
nex[p][c] = clone, p = link[p];
}
link[clone] = link[q], link[cur] = clone, link[q] = clone;
return cur;
}
int insertTire(int cur, int c){ // 先建立字典树
if(!nex[cur][c]) nex[cur][c] = tot++;
return nex[cur][c];
}
void insert(string s){
custr++;
int root = 0;
for(auto ch : s) {
root = insertTire(root, ch - 'a');
seTree.insert(1, m, custr, seTree.root[root]); // 该节点模式串custr的endpoint数量 + 1
}
}
void insert2(string s){ // 文本串插入字典树
int root = 0;
int sz = s.size();
for(int i = 0; i < sz; i++) {
root = insertTire(root, s[i] - 'a');
pos[i] = root; // 记录文本串每个endpoint对应的最初的节点(也就是最深的拥有以 i 为结尾的点)
}
}
void Build(){ // 建立后缀自动机
queue<pii> que;
for(int i = 0; i < char_num; i++){
if(nex[0][i]) que.push({i, 0});
}
while(que.size()){
auto item = que.front();
que.pop();
auto last = insert_SAM(item.se, item.fi);
for(int i = 0; i < char_num; i++){
if(nex[last][i]) que.push({i, last});
}
}
}
void BuildTree(){ // 建立link树
for(int i = 0; i < tot; i++) head[i] = -1;
for(int i = 1; i < tot; i++){
add(i, link[i]);
add(link[i], i);
}
}
int fa[maxn][30], depth[maxn];
void dfs(int u, int pre, int d){
fa[u][0] = pre, depth[u] = d;
for(int i = head[u]; i != -1; i = edge[i].next){
int to = edge[i].to;
if(to == pre) continue;
dfs(to, u, d + 1);
}
}
void init(int root){ // 倍增处理lca
dfs(root, -1, 0);
for(int j = 0; (1 << (j + 1)) < tot - 1; j++){
for(int i = 1; i <= tot - 1; i++){
if(fa[i][j] < 0) fa[i][j + 1] = -1;
else fa[i][j + 1] = fa[fa[i][j]][j];
}
}
}
int Find(int x, int nlen){
int u = pos[x];
for(int i = 20; i >= 0; i--){
if(fa[u][i] != -1 && len[fa[u][i]] >= nlen) {
u = fa[u][i];
}
}
return u;
}
} exSam;
string s, t;
int main(int argc, char const *argv[])
{
cin >> s;
exSam.init();
exSam.insert2(s);
cin >> m;
for(int i = 1; i <= m; i++){
cin >> s;
exSam.insert(s);
}
exSam.Build();
exSam.BuildTree();
exSam.init(0);
seTree.dfs(0, -1);
cin >> n;
while(n--){
int le, ri, L, R;
scanf("%d%d%d%d", &le, &ri, &L, &R);
L--, R--;
int len = R - L + 1;
int u = exSam.Find(R, len);
pii res = seTree.Query(1, m, le, ri, seTree.root[u]);
if(res.se == 0) res.se = le;// 坑点,所选模式串不存在该字串,则要输出le
printf("%d %d\n", res.se, res.fi);
}
return 0;
}