传送门


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

需要解决两个问题:

1、如何快速找到\(S[pl...pr]\)在SAM上对应的状态。

因为题目没有给\(\sum pr - pl\)的条件,所以不能暴力,考虑一个一个字符加进去,每加入一个字符计算对应答案。将询问按照\(pr\)从小到大排序然后扫描线:把模板串放在广义SAM上匹配。设插入第\(i\)个字符时到达的状态为\(u\)、匹配长度为\(len\),那么对于某个询问\(S[x...i]\),若\(len < x - i + 1\)则次数为\(0\)。

而\(len \geq x - i + 1\)时不代表可以在\(u\)上计算答案,可能\(u\)状态包含的串中不包含\(S[x...i]\)。此时还要跳\(father\)直到\(Shortest_u \leq x - i + 1\),状态\(u\)才是需要计算的状态。这里可以倍增优化跳的过程。

2、如何快速计算\(T_i\)转移到状态\(u\)的次数。

可以发现这个跟计算\(endpos\)集合有些类似,即如果某个状态\(u\)被标记为“串\(T_i\)可以走到”,那么它的祖先也一定会有这个标记。故每个节点维护一棵线段树记录每一个串在该状态的标记次数,使用线段树合并把\(parent\)树上所有节点的线段树处理出来,就可以每一次\(O(logn)\)地询问。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<vector>
#include<algorithm>
#include<cstring>
#include<queue>
#include<iomanip>
#define PII pair < int , int >
#define st first
#define nd second
//This code is written by Itst
using namespace std; const int MAXN = 5e5 + 7 , MAXL = 1e5 + 7;
char s[MAXL] , S[MAXN];
int M , Q , cnt = 1 , ch[MAXL][26] , dep[MAXL] , ind[MAXL];
vector < int > bel[MAXL];
struct Query{
int a , b , c , d , ind;
bool operator <(const Query a)const{return d < a.d;}
};
vector < Query > que;
PII ans[MAXN]; namespace Tree{
struct node{
int l , r , maxN , sz , ind;
}Tree[MAXL * 60];
int rt[MAXL] , cnt = 1; #define lch (Tree[x].l)
#define rch (Tree[x].r)
#define mid ((l + r) >> 1)
inline void pushup(int x){
if(Tree[rch].maxN > Tree[lch].maxN){
Tree[x].maxN = Tree[rch].maxN;
Tree[x].ind = Tree[rch].ind;
}
else{
Tree[x].maxN = Tree[lch].maxN;
Tree[x].ind = Tree[lch].ind;
}
} int insert(int x , int l , int r , int tar){
if(!x) x = ++cnt;
++Tree[x].sz;
if(l == r){
Tree[x].maxN = Tree[x].sz;
Tree[x].ind = l;
return x;
}
if(mid >= tar) lch = insert(lch , l , mid , tar);
else rch = insert(rch , mid + 1 , r , tar);
pushup(x);
return x;
} PII query(int x , int l , int r , int L , int R){
if(l >= L && r <= R)
return PII(Tree[x].maxN , -Tree[x].ind);
PII sum(0 , 0);
if(mid >= L)
sum = max(sum , query(lch , l , mid , L , R));
if(mid < R)
sum = max(sum , query(rch , mid + 1 , r , L , R));
return sum;
} int merge(int p , int q , int l , int r){
if(!p || !q) return p + q;
int t = ++cnt;
Tree[t].sz = Tree[p].sz + Tree[q].sz;
if(l == r){
Tree[t].maxN = Tree[t].sz;
Tree[t].ind = l;
}
else{
Tree[t].l = merge(Tree[p].l , Tree[q].l , l , mid);
Tree[t].r = merge(Tree[p].r , Tree[q].r , mid + 1 , r);
pushup(t);
}
return t;
}
} namespace SAM{
int Lst[MAXL] , Sst[MAXL] , fa[MAXL] , trans[MAXL][26];
int cnt = 1; int insert(int p , int len , int x){
int t = ++cnt; Lst[t] = len;
while(p && !trans[p][x]){
trans[p][x] = t;
p = fa[p];
}
if(!p){
Sst[t] = fa[t] = 1;
return t;
}
int q = trans[p][x];
Sst[t] = Lst[p] + 2;
if(Lst[q] == Lst[p] + 1){
fa[t] = q;
return t;
}
int k = ++cnt;
memcpy(trans[k] , trans[q] , sizeof(trans[k]));
Lst[k] = Lst[p] + 1; Sst[k] = Sst[q];
Sst[q] = Lst[p] + 2;
fa[k] = fa[q]; fa[q] = fa[t] = k;
while(trans[p][x] == q){
trans[p][x] = k;
p = fa[p];
}
return t;
} vector < int > ch[MAXL] , bel[MAXL];
int jump[MAXL][20]; void dfs(int x){
for(auto &t : bel[x])
Tree::rt[x] = Tree::insert(Tree::rt[x] , 1 , M , t);
for(int i = 1 ; i <= 18 ; ++i)
jump[x][i] = jump[jump[x][i - 1]][i - 1]; for(auto &t : ch[x]){
jump[t][0] = x;
dfs(t);
Tree::rt[x] = Tree::merge(Tree::rt[x] , Tree::rt[t] , 1 , M);
}
} void build(){
for(int i = 2 ; i <= cnt ; ++i)
ch[fa[i]].push_back(i);
dfs(1);
} int calc(int x , int l){
if(Sst[x] <= l) return x;
for(int i = 18 ; i >= 0 ; --i)
if(Sst[jump[x][i]] > l)
x = jump[x][i];
return jump[x][0];
}
} void insert(int ind){
scanf("%s" , s + 1);
int L = strlen(s + 1) , cur = 1;
for(int i = 1 ; i <= L ; ++i){
if(!ch[cur][s[i] - 'a'])
dep[ch[cur][s[i] - 'a'] = ++cnt] = dep[cur] + 1;
bel[cur = ch[cur][s[i] - 'a']].push_back(ind);
}
} void build(){
queue < int > q;
q.push(ind[1] = 1);
while(!q.empty()){
int t = q.front();
q.pop();
SAM::bel[ind[t]] = bel[t];
for(int i = 0 ; i < 26 ; ++i)
if(ch[t][i]){
ind[ch[t][i]] = SAM::insert(ind[t] , dep[ch[t][i]] , i);
q.push(ch[t][i]);
}
}
SAM::build();
} int main(){
#ifndef ONLINE_JUDGE
freopen("in" , "r" , stdin);
//freopen("out" , "w" , stdout);
#endif
scanf("%s %d" , S + 1 , &M);
for(int i = 1 ; i <= M ; ++i)
insert(i);
build();
scanf("%d" , &Q);
for(int i = 1 ; i <= Q ; ++i){
Query now;
scanf("%d %d %d %d" , &now.a , &now.b , &now.c , &now.d);
now.ind = i;
que.push_back(now);
}
sort(que.begin() , que.end());
int bef = 0 , u = 1 , len = 0;
for(auto &q : que){
while(bef < q.d){
++bef;
while(u != 1 && !SAM::trans[u][S[bef] - 'a'])
len = SAM::Lst[u = SAM::fa[u]];
if(SAM::trans[u][S[bef] - 'a']){
++len;
u = SAM::trans[u][S[bef] - 'a'];
}
}
if(len >= q.d - q.c + 1){
int t = SAM::calc(u , q.d - q.c + 1);
ans[q.ind] = Tree::query(Tree::rt[t] , 1 , M , q.a , q.b);
}
if(!ans[q.ind].st)
ans[q.ind].nd = -q.a;
}
for(int i = 1 ; i <= Q ; ++i)
printf("%d %d\n" , -ans[i].nd , ans[i].st);
return 0;
}

CF666E Forensic Examination 广义SAM、线段树合并、倍增、扫描线的更多相关文章

  1. CodeForces - 666E: Forensic Examination (广义SAM 线段树合并)

    题意:给定字符串S,然后M个字符串T.Q次询问,每次给出(L,R,l,r),问S[l,r]在L到R这些T字符串中,在哪个串出现最多,以及次数. 思路:把所有串建立SAM,然后可以通过倍增走到[l,r] ...

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

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

  3. CF666E-Forensic Examination【广义SAM,线段树合并】

    正题 题目链接:https://www.luogu.com.cn/problem/CF666E 解题思路 给出一个串\(S\)和\(n\)个串\(T_i\).\(m\)次询问\(S_{a\sim b} ...

  4. 【Codeforces666E】Forensic Examination 后缀自动机 + 线段树合并

    E. Forensic Examination time limit per test:6 seconds memory limit per test:768 megabytes input:stan ...

  5. CF204E-Little Elephant and Strings【广义SAM,线段树合并】

    正题 题目链接:https://www.luogu.com.cn/problem/CF204E 题目大意 \(n\)个字符串的一个字符串集合,对于每个字符串求有多少个子串是这个字符串集合中至少\(k\ ...

  6. YbtOJ#532-往事之树【广义SAM,线段树合并】

    正题 题目链接:https://www.ybtoj.com.cn/problem/532 题目大意 给出\(n\)个点的一个\(Trie\)树,定义\(S_x\)表示节点\(x\)代表的字符串 求$$ ...

  7. loj#2059. 「TJOI / HEOI2016」字符串 sam+线段树合并+倍增

    题意:给你一个子串,m次询问,每次给你abcd,问你子串sa-b的所有子串和子串sc-d的最长公共前缀是多长 题解:首先要求两个子串的最长公共前缀就是把反过来插入变成最长公共后缀,两个节点在paren ...

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

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

  9. CF666E Forensic Examination 广义后缀自动机_线段树合并_树上倍增

    题意: 给定一个串 $S$ 和若干个串 $T_{i}$每次询问 $S[pl..pr]$ 在 $Tl..Tr$ 中出现的最多次数,以及出现次数最多的那个串的编号. 数据范围: 需要离线 题解:首先,很常 ...

随机推荐

  1. PHP与.Net的区别(一)接口

    一.关于接口成员 PHP的接口成员只能包括两种: 1.函数签名 2.常量 .Net的接口成员只能包括三种: 1.函数签名 2.属性(注意:是属性,不是字段) 3.事件 4.索引器(也叫有参属性)

  2. ES6模块化与常用功能

    目前开发环境已经普及使用,如vue,react等,但浏览器环境却支持不好,所以需要开发环境编译,下面介绍下开发环境的使用和常用语法: 一,ES6模块化 1,模块化的基本语法 ES6 的模块自动采用严格 ...

  3. flask day01

    目标:搭建好一个flask架构,并且可以运行起来,能够访问 ## 一丶配置环境 比较简单,只需要配一个flask pip3 install flask #也可以使用pip install flask ...

  4. [20170628]完善ooerr脚本.txt

    [20170628]完善ooerr脚本.txt --//注意不是oracle的oerr,是我写的一个小脚本,下面会提到.很简单.^_^.--//参考链接:blog.itpub.net/267265/v ...

  5. JAVA开发学习

    一.安装JAVA开发工具IDEA,下载Ultimate旗舰版版本,Community社区版不支持Java EE开发...... 下载地址:https://www.jetbrains.com/idea/ ...

  6. 如何猜出 Y combinator

    先约定几个记号: 定义用一个冒号加等号表示":=", 表达式全等用两个等号表示"==", 归约意义上的相等用一个等号表示"="," ...

  7. InnoDB中锁的模式,锁的查看,算法

    InnoDB中锁的模式   Ⅰ.总览 S行级共享锁lock in share mode X行级排它锁增删改 IS意向共享锁 IX意向排他锁 AI自增锁 Ⅱ.锁之间的兼容性 兼 X IX S IS X ...

  8. 监控.net 网站 Glimpse

    使用Nuget 安装Glimpse 安装好后,config会默认添加几个节点 安装好之后 只需要浏览器输入  网站/Glimpse.axd 再次进入网站 就可以查看(ajax sql session ...

  9. PL/SQL 删除主键 ORA-02443: 无法删除约束条件-不存在的约束条件

    在PL/SQL developer中删除一个表的主键,然后把另外一个字段设置成主键,删除的过程中报错:ORA-02443 我遇到这个问题出现的背景是: alter table saleqtya dro ...

  10. Linux初学 - Centos7忘记root密码的解决办法

    开机进入启动界面后,要按照屏幕的下方的操作提示迅速按下“e”键. 按下“e”键后即来到启动文件界面,这时按键盘上面的方向键“下”,一直到文件底部,在"LANG=zh_cn.UTF-8&quo ...