$ \color{#0066ff}{ 题目描述 }$

给你一个串\(S\)以及一个字符串数组\(T[1..m]\),\(q\)次询问,每次问\(S\)的子串\(S[p_l..p_r]\)在\(T[l..r]\)中的哪个串里的出现次数最多,并输出出现次数。

如有多解输出最靠前的那一个。

\(\color{#0066ff}{输入格式}\)

第一行一个个字符串表示\(S\)

第二行一个数字\(m\)

接下来\(m\)行每行一个字符串表示\(T[i]\)

接下来一行一个数字\(q\)表示询问次数

接下来\(q\)行每行四个数字\(l,r,ql,qr\)表示一个询问

\(\color{#0066ff}{输出格式}\)

共\(q\)行每行两个数字表示出现位置和次数

\(\color{#0066ff}{输入样例}\)

suffixtree
3
suffixtreesareawesome
cartesiantreeisworsethansegmenttree
nyeeheeheee
2
1 2 1 10
1 3 9 10

\(\color{#0066ff}{输出样例}\)

1 1
3 4

\(\color{#0066ff}{数据范围与提示}\)

\(1<=∣s∣<=5*10^5,1<=m<=5*10^4,1<=q<=5*10^5\)

\(\color{#0066ff}{题解}\)

考虑暴力,我们可以建立m个后缀自动机,然后暴力匹配, 复杂度是\(O(nmq)\)的

这样无论如何都要枚举m个后缀自动机,复杂度是无法优化的

我们考虑另一种思路,建立广义后缀自动机,每个节点记录一下属于哪个串bel,那么如果我们匹配之后到了某个点,我们需要在这个点的前缀树的子树内找到一个出现次数最多的bel且bel尽量小(靠左)

也就是找子树最大值以及位置,我们考虑对前缀树上的每个点开一个动态开点线段树,维护子树内bel的最大值以及位置,这个刚开始可以先在extend中记录维护自己的东西,然后dfs前缀树,把子树内的线段树合并,这样每个点维护的就是子树内的线段树了

于是对于一个询问,我们暴力匹配它的字串,跳到那个点,然后在那个点的线段树上查询就行了

于是我们的复杂度由\(O(nmq)\)降到了\(O(q * n * logm)\)

但是这样依然是过不了,时间浪费在了匹配上

我们考虑优化匹配这个过程

考虑在前缀树上倍增,因为每次跳父亲就是删去前面若干字符,所以我们可以先预处理出S的每个前缀在后缀自动机上的匹配情况,记录一下这个前缀的最长匹配后缀和匹配到的节点

对于一个询问, 字串\([l,r]\),我们直接找到\(S[1...r]\)的匹配节点,然后倍增往上跳,删去l前面的节点, 就是我们匹配的节点,然后再线段树查询就行了

这样\(O(q*logn*logm)\)就能过了

#include<bits/stdc++.h>
#define LL long long
LL in() {
char ch; LL x = 0, f = 1;
while(!isdigit(ch = getchar()))(ch == '-') && (f = -f);
for(x = ch ^ 48; isdigit(ch = getchar()); x = (x << 1) + (x << 3) + (ch ^ 48));
return x * f;
}
const int maxn = 7e5 + 100;
struct Tree {
protected:
struct node {
node *ch[2];
int max, pos;
node(int max = 0, int pos = 0): max(max), pos(pos) { ch[0] = ch[1] = NULL; }
void upd() {
if(!ch[0] && !ch[1]) return;
if(!ch[0]) max = ch[1]->max, pos = ch[1]->pos;
else if(!ch[1]) max = ch[0]->max, pos = ch[0]->pos;
else {
if(ch[0]->max >= ch[1]->max) max = ch[0]->max, pos = ch[0]->pos;
else max = ch[1]->max, pos = ch[1]->pos;
}
}
}*root[maxn];
int n;
void add(node *&o, int l, int r, int pos, int val) {
if(!o) o = new node();
if(l == r) return (void)(o->max += val, o->pos = l);
int mid = (l + r) >> 1;
if(pos <= mid) add(o->ch[0], l, mid, pos, val);
else add(o->ch[1], mid + 1, r, pos, val);
o->upd();
}
std::pair<int, int> query(node *o, int l, int r, int ql, int qr) {
if(!o) return std::make_pair(0, ql);
if(ql <= l && r <= qr) return std::make_pair(o->max, o->pos);
int mid = (l + r) >> 1;
if(qr <= mid) return query(o->ch[0], l, mid, ql, qr);
if(ql > mid) return query(o->ch[1], mid + 1, r, ql, qr);
std::pair<int, int> ansl = query(o->ch[0], l, mid, ql, qr);
std::pair<int, int> ansr = query(o->ch[1], mid + 1, r, ql, qr);
if(ansl.first >= ansr.first) return ansl;
return ansr;
}
node *merge(node *x, node *y, int l, int r) {
if(!x || !y) return x? x : y;
node *o = new node();
if(l == r) return o->max = x->max + y->max, o->pos = l, o;
int mid = (l + r) >> 1;
o->ch[0] = merge(x->ch[0], y->ch[0], l, mid);
o->ch[1] = merge(x->ch[1], y->ch[1], mid + 1, r);
return o->upd(), o;
}
public:
void set(int n) { this->n = n; }
void add(int id, int pos, int val) { add(root[id], 1, n, pos, val); }
std::pair<int, int> query(int id, int l, int r) { return query(root[id], 1, n, l, r); }
void merge(int x, int y) { root[x] = merge(root[x], root[y], 1, n); }
}T;
struct node {
node *ch[26], *fa;
std::vector<node *> treech;
int len;
node(int len = 0): len(len) {
memset(ch, 0, sizeof ch); fa = NULL; treech.clear();
}
}pool[maxn << 1], *tail, *lst, *root;
void init() {
tail = pool;
root = lst = new(tail++) node();
}
void extend(int c, int id) {
node *o = new(tail++) node(lst->len + 1), *v = lst;
T.add(o - pool, id, 1);
for(; v && !v->ch[c]; v = v->fa) v->ch[c] = o;
if(!v) o->fa = root;
else if(v->len + 1 == v->ch[c]->len) o->fa = v->ch[c];
else {
node *n = new(tail++) node(v->len + 1), *d = v->ch[c];
std::copy(d->ch, d->ch + 26, n->ch);
n->fa = d->fa, d->fa = o->fa = n;
for(; v && v->ch[c] == d; v = v->fa) v->ch[c] = n;
}
lst = o;
}
int f[maxn << 1][22];
void build() {
for(node *o = pool + 1; o != tail; o++) {
o->fa->treech.push_back(o);
f[o - pool][0] = o->fa - pool;
}
}
void dfs(node *o) {
for(node *to : o->treech) dfs(to), T.merge(o - pool, to - pool);
}
char s[maxn], ls[maxn];
int len, m, matchmax[maxn], matchpos[maxn];
void match() {
int nowlen = 0; node *o = root;
for(int i = 1; i <= len; i++) {
int p = s[i] - 'a';
if(o->ch[p]) o = o->ch[p], nowlen++;
else {
while(o != root && !o->ch[p]) o = o->fa, nowlen = o->len;
if(o->ch[p]) o = o->ch[p], nowlen++;
}
matchmax[i] = nowlen;
matchpos[i] = o - pool;
}
}
void getans(int ql, int qr, int l, int r) {
if(matchmax[r] < r - l + 1) return (void)(printf("%d %d\n", ql, 0));
int x = matchpos[r];
for(int i = 20; i >= 0; i--) if((pool + f[x][i])->len >= r - l + 1) x = f[x][i];
std::pair<int, int> ans = T.query(x, ql, qr);
printf("%d %d\n", ans.second, ans.first);
} int main() {
scanf("%s", s + 1);
len = strlen(s + 1);
T.set(m = in()); init();
for(int i = 1; i <= m; i++) {
scanf("%s", ls);
lst = root;
for(char *p = ls; *p; p++) extend(*p - 'a', i);
}
build();
dfs(root);
match();
for(int j = 1; j <= 20; j++)
for(int i = 1; i <= tail - pool - 1; i++)
f[i][j] = f[f[i][j - 1]][j - 1];
int l, r, ql, qr;
for(int T = in(); T --> 0;) {
l = in(), r = in(), ql = in(), qr = in();
getans(l, r, ql, qr);
}
return 0;
}

CF666E Forensic Examination SAM+线段树合并+前缀树倍增的更多相关文章

  1. CF666E Forensic Examination——SAM+线段树合并+倍增

    RemoteJudge 题目大意 给你一个串\(S\)以及一个字符串数组\(T[1...m]\),\(q\)次询问,每次问\(S\)的子串\(S[p_l...p_r]\)在\(T[l...r]\)中的 ...

  2. CF666E Forensic Examination SAM+倍增,线段树和并

    题面: 给你一个串S以及一个字符串数组T[1..m],q次询问,每次问S的子串S[p_l..p_r]在T[l..r]中的哪个串里的出现次数最多,并输出出现次数.如有多解输出最靠前的那一个. 分析: 第 ...

  3. 2016湖南省赛 I Tree Intersection(线段树合并,树链剖分)

    2016湖南省赛 I Tree Intersection(线段树合并,树链剖分) 传送门:https://ac.nowcoder.com/acm/contest/1112/I 题意: 给你一个n个结点 ...

  4. 208 Implement Trie (Prefix Tree) 字典树(前缀树)

    实现一个 Trie (前缀树),包含 insert, search, 和 startsWith 这三个方法.注意:你可以假设所有的输入都是小写字母 a-z.详见:https://leetcode.co ...

  5. CF666E Forensic Examination 广义SAM、线段树合并、倍增、扫描线

    传送门 朴素想法:对\(M\)个匹配串\(T_1,...,T_M\)建立广义SAM,对于每一次询问,找到这个SAM上\(S[pl...pr]\)对应的状态,然后计算出对于每一个\(i \in [l,r ...

  6. Codeforces 666E Forensic Examination SAM or SA+线段树合并

    E. Forensic Examination http://codeforces.com/problemset/problem/666/E 题目大意:给模式串S以及m个特殊串,q个询问,询问S的子串 ...

  7. CF666E Forensic Examination(后缀自动机+线段树合并)

    给你一个串S以及一个字符串数组T[1..m],q次询问,每次问S的子串S[pl..pr]在T[l..r]中的哪个串里的出现次数最多,并输出出现次数. 如有多解输出最靠前的那一个. 我们首先对m个字符串 ...

  8. CF666E Forensic Examination [后缀自动机,线段树合并]

    洛谷 Codeforces 思路 最初的想法:后缀数组+区间众数,似乎并不能过. 既然后缀数组不行,那就按照套路建出广义SAM,然后把\(S\)放在上面跑,得到以每个点结尾会到SAM上哪个节点. 询问 ...

  9. cf666E. Forensic Examination(广义后缀自动机 线段树合并)

    题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...

随机推荐

  1. Opencv Laplace算子

    //通过拉普拉斯-锐化边缘 kernel = (Mat_<float>(3,3)<<1,1,1,1,-8,1,1,1,1);//Laplace算子 filter2D(img2, ...

  2. 路由软件quagga和bird日志配置打印ospf邻居变化

    背景: 网络侧反馈偶尔会出现ospf邻居状态变化:full-> other status -> full.历史原因,线上运行的路由软件有quagga和bird两种.两种路由软件的日志级别配 ...

  3. 2-字符串模拟- URL映射

    问题描述 试题编号: 201803-3 试题名称: URL映射 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 URL 映射是诸如 Django.Ruby on Rails 等 ...

  4. 转载 MYSQL性能优化的最佳20+条经验

    转自:https://coolshell.cn/articles/1846.html 今天,数据库的操作越来越成为整个应用的性能瓶颈了,这点对于Web应用尤其明显.关于数据库的性能,这并不只是DBA才 ...

  5. 633E Binary Table

    传送门 分析 我们发现n特别小,所以可以从这里入手 我们记录出所有列中某一种状态的列有多少个 我们再记录出每种列最少有多少个1(原来的1的个数和取反后的个数去最小值) 于是我们可以得出对于所有列异或一 ...

  6. redis集群部署及常用的操作命令(下)

    搭建好集群之后,为了扩容需要再加入一个节点.那就再复制一个7006,改为相应的redis.conf(复制了改个port就好,如果复制的redis之前属于集群,需要把关联的node.conf之类的去掉) ...

  7. Makefile模板

    CC = gcc LD = gcc CFLAGS = -Wall -c LDFLAGS = SRC_DIRS = src test INC_DIRS = inc OBJ_DIR = obj OUT_D ...

  8. 设计模式13---桥接模式(Bridge Pattern)

    桥接模式将抽象与具体实现分离,使得抽象与具体实现可以各自改变互不影响.桥接模式属于设计模式中的结构模式. 桥梁模式涉及的角色 抽象(Abstraction)角色:抽象定义,引用对接口对象的引用. 重新 ...

  9. SpringCloud教程 | 第二篇: 服务消费者(rest+ribbon)(Finchley版本)

    在上一篇文章,讲了服务的注册和发现.在微服务架构中,业务都会被拆分成一个独立的服务,服务与服务的通讯是基于http restful的.Spring cloud有两种服务调用方式,一种是ribbon+r ...

  10. HashMap的小试牛刀

    HashMap的介绍 import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.ut ...