CF 666E Forensic Examination 【SAM 倍增 线段树合并】
CF 666E Forensic Examination
题意:
给出一个串\(s\)和\(n\)个串\(t_i\),\(q\)次询问,每次询问串\(s\)的子串\(s[p_l:p_r]\)在串\(t_l\)到\(t_r\)中哪个串中出现次数最多,以及出现次数最多的哪个串的下标
题解:
考虑把\(n\)个\(t\)串建出广义后缀自动机,然后后缀自动机上每个节点用动态开点线段树来维护每个\(t\)串能匹配到的数量,把每个\(t\)串的每个后缀能匹配的最长的串对应的后缀自动机上的点以当前\(t\)串的下标在当前点的线段树上加一,然后做线段树合并
对于串\(s\),记录以每个位置为右端点的能在自动机上匹配的最长子串长度,以及在自动机上对应的点,用类似做\(LCS\)的的办法来做,就是不断跳\(link\)来到达匹配的点
对于每次询问,找到自动机上对应\(s[p_l:p_r]\)的节点,这个可以从以\(s[p_r]\)为右端点的最长匹配串对应的自动机上的点不断跳\(parent\)树找到,可以用倍增来优化,然后直接查询区间最大值和最大值下标即可
要注意\(s[p_l:p_r]\)在自动机上无匹配点的情况,特判一下即可
view code
//#pragma GCC optimize("O3")
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<bits/stdc++.h>
using namespace std;
function<void(void)> ____ = [](){ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);};
const int MAXN = 5e5+7;
struct SegmentTree{
int tot, ls[MAXN<<5], rs[MAXN<<5], root[MAXN];
pair<int,int> maxx[MAXN<<5];
SegmentTree(){ memset(root,0,sizeof(root)); tot = 0; }
int newnode(){ tot++; ls[tot] = rs[tot] = 0; maxx[tot] = make_pair(0,0); return tot; }
void pushup(int rt){
if(maxx[ls[rt]].first>=maxx[rs[rt]].first) maxx[rt] = maxx[ls[rt]];
else maxx[rt] = maxx[rs[rt]];
}
void modify(int &rt, int pos, int L, int R, int x){
if(!rt) rt = newnode();
if(L + 1 == R){
maxx[rt].first += x;
maxx[rt].second = L;
return;
}
int mid = (L + R) >> 1;
if(pos < mid) modify(ls[rt],pos,L,mid,x);
else modify(rs[rt],pos,mid,R,x);
pushup(rt);
}
pair<int,int> ask(int L, int R, int l, int r, int rt){
if(!rt or l>=R or L>=r) return make_pair(0,MAXN);
if(L<=l and r<=R) return maxx[rt];
int mid = (l + r) >> 1;
auto p1 = ask(L,R,l,mid,ls[rt]);
auto p2 = ask(L,R,mid,r,rs[rt]);
if(p1.first>=p2.first) return p1;
else return p2;
}
int merge(int u, int v, int L, int R){
if(!u or !v) return u | v;
if(L + 1 == R){
maxx[u].first += maxx[v].first;
return u;
}
int mid = (L + R) >> 1;
ls[u] = merge(ls[u],ls[v],L,mid);
rs[u] = merge(rs[u],rs[v],mid,R);
pushup(u);
return u;
}
};
struct Trie{
int tot, ch[MAXN][26];
Trie():tot(0){ memset(ch,0,sizeof(ch)); }
void insert(const char *s){
int u = 0, n = strlen(s);
for(int i = 0; i < n; i++){
int c = s[i] - 'a';
if(!ch[u][c]) ch[u][c] = ++tot;
u = ch[u][c];
}
}
};
struct Suffix_automaton{
Trie trie;
SegmentTree seg;
int SEGTREE_SIZE;
int tot, ch[MAXN][26], pos[MAXN], link[MAXN], par[MAXN][20], len[MAXN];
vector<int> G[MAXN];
Suffix_automaton(){ tot = 0; link[tot] = -1; }
int extend(int c, int last){
int p = last, np = ++tot;
len[np] = len[last] + 1;
while(p!=-1 and !ch[p][c]) ch[p][c] = np, p = link[p];
if(p==-1) link[np] = 0;
else{
int q = ch[p][c];
if(len[p]+1==len[q]) link[np] = q;
else{
int clone = ++tot;
len[clone] = len[p] + 1;
link[clone] = link[q];
memcpy(ch[clone],ch[q],sizeof(ch[q]));
while(p!=-1 and ch[p][c]==q) ch[p][c] = clone, p = link[p];
link[q] = link[np] = clone;
}
}
return np;
}
void dfs(int u){
par[u][0] = link[u];
for(int i = 1; ~par[u][i-1]; i++) par[u][i] = par[par[u][i-1]][i-1];
for(int v : G[u]) dfs(v);
}
void build(){
pos[0] = 0;
queue<int> que;
que.push(0);
while(!que.empty()){
int u = que.front(); que.pop();
for(int c = 0; c < 26; c++){
if(!trie.ch[u][c]) continue;
int v = trie.ch[u][c];
pos[v] = extend(c,pos[u]);
que.push(v);
}
}
for(int i = 1; i <= tot; i++) G[link[i]].push_back(i);
memset(par,255,sizeof(par));
dfs(0);
}
void modify(string &s, int id){
int u = 0;
for(int i = 0; i < (int)s.length(); i++){
int c = s[i] - 'a';
u = ch[u][c];
seg.modify(seg.root[u],id,1,SEGTREE_SIZE+1,1);
}
}
}sam;
struct Query{
int l, r, qid;
Query(){}
Query(int _l, int _r, int id):l(_l),r(_r),qid(id){}
};
vector<Query> Q[MAXN];
char s[MAXN], buf[MAXN];
int n, m, sampos[MAXN], matlen[MAXN], l;
pair<int,int> ret[MAXN];
vector<string> vec_s;
void dfsMerge(int u){
for(int v : sam.G[u]){
dfsMerge(v);
sam.seg.root[u] = sam.seg.merge(sam.seg.root[u],sam.seg.root[v],1,sam.SEGTREE_SIZE+1);
}
for(auto qs : Q[u]){
auto p = sam.seg.ask(qs.l,qs.r+1,1,sam.SEGTREE_SIZE+1,sam.seg.root[u]);
if(p.first==0) ret[qs.qid] = make_pair(0,qs.l);
else ret[qs.qid] = p;
}
}
void solve(){
scanf("%s %d",s + 1,&n);
l = strlen(s + 1);
sam.SEGTREE_SIZE = n;
vec_s.resize(n);
for(int i = 0; i < n; i++){
scanf("%s",buf);
vec_s[i] = string(buf);
sam.trie.insert(buf);
}
sam.build();
for(int i = 0; i < n; i++) sam.modify(vec_s[i], i+1);
int cur = 0, mat = 0;
for(int i = 1; i <= l; i++){
int c = s[i] - 'a';
if(sam.ch[cur][c]) cur = sam.ch[cur][c], mat++;
else{
while(cur!=-1 and !sam.ch[cur][c]) cur = sam.link[cur];
if(cur==-1) cur = 0, mat = 0;
else mat = sam.len[cur] + 1, cur = sam.ch[cur][c];
}
sampos[i] = cur;
matlen[i] = mat;
}
scanf("%d",&m);
for(int i = 1; i <= m; i++){
int l, r, pl, pr;
scanf("%d %d %d %d",&l,&r,&pl,&pr);
int lth = pr - pl + 1;
int p = sampos[pr];
if(matlen[pr]<lth) ret[i] = make_pair(0,l);
else{
for(int j = 19; ~j; j--) if(sam.par[p][j]!=-1 and sam.len[sam.par[p][j]]>=lth) p = sam.par[p][j];
Q[p].push_back(Query(l,r,i));
}
}
dfsMerge(0);
for(int i = 1; i <= m; i++) cout << ret[i].second << ' ' << ret[i].first << endl;
}
int main(){
#ifndef ONLINE_JUDGE
freopen("Local.in","r",stdin);
// freopen("ans.out","w",stdout);
#endif
solve();
return 0;
}
CF 666E Forensic Examination 【SAM 倍增 线段树合并】的更多相关文章
- 【Codeforces666E】Forensic Examination 后缀自动机 + 线段树合并
E. Forensic Examination time limit per test:6 seconds memory limit per test:768 megabytes input:stan ...
- 【codeforces666E】Forensic Examination 广义后缀自动机+树上倍增+线段树合并
题目描述 给出 $S$ 串和 $m$ 个 $T_i$ 串,$q$ 次询问,每次询问给出 $l$ .$r$ .$x$ .$y$ ,求 $S_{x...y}$ 在 $T_l,T_{l+1},...,T_r ...
- Codeforces 666E Forensic Examination SAM or SA+线段树合并
E. Forensic Examination http://codeforces.com/problemset/problem/666/E 题目大意:给模式串S以及m个特殊串,q个询问,询问S的子串 ...
- CF666E Forensic Examination 广义SAM、线段树合并、倍增、扫描线
传送门 朴素想法:对\(M\)个匹配串\(T_1,...,T_M\)建立广义SAM,对于每一次询问,找到这个SAM上\(S[pl...pr]\)对应的状态,然后计算出对于每一个\(i \in [l,r ...
- CF666E Forensic Examination SAM+倍增,线段树和并
题面: 给你一个串S以及一个字符串数组T[1..m],q次询问,每次问S的子串S[p_l..p_r]在T[l..r]中的哪个串里的出现次数最多,并输出出现次数.如有多解输出最靠前的那一个. 分析: 第 ...
- CF 666E Forensic Examination——广义后缀自动机+线段树合并
题目:http://codeforces.com/contest/666/problem/E 对模式串建广义后缀自动机,询问的时候把询问子串对应到广义后缀自动机的节点上,就处理了“区间”询问. 还要处 ...
- Codeforces 666E Forensic Examination SAM+权值线段树
第一次做这种$SAM$带权值线段树合并的题 然而$zjq$神犇看完题一顿狂码就做出来了 $Orz$ 首先把所有串当成一个串建$SAM$ 我们对$SAM$上每个点 建一棵权值线段树 每个叶子节点表示一个 ...
- 【CF666E】Forensic Examination 广义后缀自动机+倍增+线段树合并
[CF666E]Forensic Examination 题意:给你一个字符串s和一个字符串集合$\{t_i\}$.有q个询问,每次给出$l,r,p_l,p_r$,问$s[p_l,p_r]$在$t_l ...
- [CF 666E] Forensic Examination
Description 传送门 Solution 对 \(T[1..m]\) 建立广义后缀自动机,离线,找出代表 \(S[pl,pr]\) 的每个节点,线段树合并. Code #include < ...
随机推荐
- ios iphone 崩溃字符记录
如题,近日iphone被爆出有一串字符可引发系统错误 (بٍٍٍٍََُُُِّّّْرٍٍٍٍََُُِِّّّْآٍٍٍَُّ بٍٍٍٍََُُُِّّّْرٍٍٍٍََُُِِّّّْآٍٍٍ ...
- sublime python 去掉单行超出字数的白色框框 (E501)
方法一 E501错误:行过长 (大于79个字符),在配置文件里设置 忽略E501错误即可 首选项-->Package Settings-->Anaconda-->Settings - ...
- 执行py文件需要可执行权限吗?
案例解析 这个问题描述起来有点违反直觉,要执行一个文件难道不应该需要可执行权限吗?让我们先来看一个例子: # module1.py def test(): print ('hello world!') ...
- Spring源码深度解析之事务
Spring源码深度解析之事务 目录 一.JDBC方式下的事务使用示例 (1)创建数据表结构 (2)创建对应数据表的PO (3)创建表和实体之间的映射 (4)创建数据操作接口 (5)创建数据操作接口实 ...
- 跟我一起学Redis之加个哨兵让主从复制更加高可用
前言 主从复制的实现在上一篇已经分享过,虽然主从复制本身的确让读写分离更加高效,但是对于整体高可用存在很大的劣势:当主节点宕机了之后还需要人为重新进行主从关系配置:这不是开玩笑嘛,这样人为干预,故障恢 ...
- 【十天自制软渲染器】DAY 02:画一条直线(DDA 算法 & Bresenham’s 算法)
推荐关注公众号「卤蛋实验室」或访问博客原文,更新更及时,阅读体验更佳 第一天我们搭建了 C++ 的运行环境并画了一个点,根据 点 → 线 → 面 的顺序,今天我们讲讲如何画一条直线. 本文主要讲解直线 ...
- 【Oracle】11g direct path read介绍:10949 event、_small_table_threshold与_serial_direct_read
转自刘相兵老师的博文: http://www.askmaclean.com/archives/11g-direct-path-read-10949-_small_table_threshold-_se ...
- 【Linux】用find删除大于30天的文件
1.删除文件命令: find 对应目录 -mtime +天数 -name "文件名" -exec rm -rf {} \; 实例命令:find /opt/soft/log/ -mt ...
- apiAutoTest: 接口自动化测试的数据清洗(备份/恢复)处理方案
接口自动化测试之数据清洗/隔离/备份/恢复 在得到QQ:1301559180 得代码贡献之后,想到了通过ssh连接上服务器,然后进行数据库备份,数据库恢复, 主要使用了 paramiko库 最终效果 ...
- XSS - Labs 靶场笔记(上)
上周在网上看到的一个XSS平台,刷一波<doge Less - 1: 1.进入主界面,由图二可知是GET请求,提交name=test,回显在页面 2.查看源代码可知 没有做任何过滤,显然存在反射 ...