CF666E Forensic Examination(后缀自动机+动态线段树)
题意
给你一个串 $S$ 以及一个字符串数组 $T[1..m]$ , $q$ 次询问,每次问 $S$ 的子串 $S[p_l..p_r]$ 在 $T[l..r]$ 中的哪个串里的出现次数最多,并输出出现次数。如有多解输出最靠前的那一个。
题解
神仙题……虽然的确是好题……然而码量好大……好麻烦……因为一个sb错误调了一个下午
先膜一波zsy大佬……%%%
首先先把$T$给建出一个广义$SAM$。考虑每一个询问的$p_r$,如果在$SAM$上匹配到了一个节点,那么这个子串$S[p_l,p_r]$一定是这个节点的一个祖先(然后如果根本匹配不到的话直接跳过)
然后可以考虑倍增找到祖先,只要找到满足$len_u>=p_r-p_l+1$的最上面的节点就好了
然后考虑如何表示这个节点在$[l,r]$范围内在哪些串出现了多少次,以及出现次数的最大值。区间查询可以考虑用线段树。对于每一个$SAM$上的节点,我们对他建一棵线段树,表示有这个节点代表的字符串在$T$中的出现次数。然后通过倍增找到节点,在线段树上查询就是了
于是直接把串$S$放到$SAM$上跑,然后在每一个节点记录一下有哪些询问在这个点上,一波$dfs$的时候顺便合并父亲和儿子的出现次数的区间,并求出答案
说的好像很简单……然而真的码得有点想哭……
//minamoto
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
inline int read(){
#define num ch-'0'
char ch;bool flag=;int res;
while((ch=getchar())<''||ch>'')
(ch=='-')&&(flag=true);
for(res=num;(ch=getchar())>=''&&ch<='';res=res*+num);
(flag)&&(res=-res);
#undef num
return res;
}
char sr[<<],z[];int C=-,Z;
inline void Ot(){fwrite(sr,,C+,stdout),C=-;}
inline void print(int x,char ch){
if(C><<)Ot();if(x<)sr[++C]=,x=-x;
while(z[++Z]=x%+,x/=);
while(sr[++C]=z[Z],--Z);sr[++C]=ch;
}
const int N=5e5+;
struct data{
int x,y;
inline bool operator <(const data &b)const
{return x<b.x||x==b.x&&y>b.y;}
}ans[N];
int L[N*],R[N*];data V[N*];
struct node{int l,r,pl,pr;}q[N];
int n,m,Q,last=,cnt=,ch[N][],fa[][N],l[N],rt[N],tot=;
int nxt[][N],head[][N];
char s[N],t[N];
void ins(int c){
int p=last,np=++cnt;last=np,l[np]=l[p]+;
for(;p&&!ch[p][c];p=fa[][p]) ch[p][c]=np;
if(!p) fa[][np]=;
else{
int q=ch[p][c];
if(l[q]==l[p]+) fa[][np]=q;
else{
int nq=++cnt;l[nq]=l[p]+;
memcpy(ch[nq],ch[q],sizeof(ch[q]));
fa[][nq]=fa[][q],fa[][q]=fa[][np]=nq;
for(;ch[p][c]==q;p=fa[][p]) ch[p][c]=nq;
}
}
}
void modify(int &now,int l,int r,int x){
now=++tot;
if(l==r) return (void)(++V[now].x,V[now].y=l);
int mid=l+r>>;
if(x<=mid) modify(L[now],l,mid,x);
else modify(R[now],mid+,r,x);
V[now]=max(V[L[now]],V[R[now]]);
}
void merge(int &x,int y){
if(!x||!y) return (void)(x|=y);
if(!L[x]&&!R[x]) return (void)(V[x].x+=V[y].x);
merge(L[x],L[y]),merge(R[x],R[y]);
V[x]=max(V[L[x]],V[R[x]]);
}
data query(int p,int l,int r,int ql,int qr){
if(ql<=l&&qr>=r) return V[p];
int mid=l+r>>;
if(qr<=mid) return query(L[p],l,mid,ql,qr);
if(ql>mid) return query(R[p],mid+,r,ql,qr);
return max(query(L[p],l,mid,ql,qr),query(R[p],mid+,r,ql,qr));
}
void dfs(int u){
for(int i=head[][u];i;i=nxt[][i])
dfs(i),merge(rt[u],rt[i]);
for(int i=head[][u];i;i=nxt[][i])
ans[i]=query(rt[u],,m,q[i].l,q[i].r);
}
int main(){
scanf("%s",s+);n=strlen(s+);
m=read();
for(int i=;i<=m;++i){
scanf("%s",t+);int len=strlen(t+);last=;
for(int j=;j<=len;++j) ins(t[j]-'a'),modify(rt[last],,m,i);
}
Q=read();
for(int i=;i<=Q;++i){
int a=read(),b=read(),c=read(),d=read();
q[i]=(node){a,b,c,d};
nxt[][i]=head[][q[i].pr],head[][q[i].pr]=i;
}
for(int i=;i<=cnt;++i)
nxt[][i]=head[][fa[][i]],head[][fa[][i]]=i;
for(int j=;j<;++j)
for(int i=;i<=cnt;++i)
fa[j][i]=fa[j-][fa[j-][i]];
for(int i=,now=,cnt=;i<=n;++i){
while(now&&!ch[now][s[i]-'a']) now=fa[][now],cnt=l[now];
if(!now){now=,cnt=;continue;}
now=ch[now][s[i]-'a'],++cnt;
for(int j=head[][i];j;j=nxt[][j]){
int u=now;if(cnt<q[j].pr-q[j].pl+) continue;
for(int k=;~k;--k)
if(l[fa[k][u]]>=q[j].pr-q[j].pl+)
u=fa[k][u];
nxt[][j]=head[][u],head[][u]=j;
}
}
dfs();
for(int i=;i<=Q;++i){
if(!ans[i].x) ans[i].y=q[i].l;
print(ans[i].y,),print(ans[i].x,);
}
Ot();
return ;
}
CF666E Forensic Examination(后缀自动机+动态线段树)的更多相关文章
- CF666E Forensic Examination [后缀自动机,线段树合并]
洛谷 Codeforces 思路 最初的想法:后缀数组+区间众数,似乎并不能过. 既然后缀数组不行,那就按照套路建出广义SAM,然后把\(S\)放在上面跑,得到以每个点结尾会到SAM上哪个节点. 询问 ...
- CF666E Forensic Examination SAM+倍增,线段树和并
题面: 给你一个串S以及一个字符串数组T[1..m],q次询问,每次问S的子串S[p_l..p_r]在T[l..r]中的哪个串里的出现次数最多,并输出出现次数.如有多解输出最靠前的那一个. 分析: 第 ...
- CF666E Forensic Examination 广义SAM、线段树合并、倍增、扫描线
传送门 朴素想法:对\(M\)个匹配串\(T_1,...,T_M\)建立广义SAM,对于每一次询问,找到这个SAM上\(S[pl...pr]\)对应的状态,然后计算出对于每一个\(i \in [l,r ...
- 【CF666E】Forensic Examination(后缀自动机,线段树合并)
[CF666E]Forensic Examination(后缀自动机,线段树合并) 题面 洛谷 CF 翻译: 给定一个串\(S\)和若干个串\(T_i\) 每次询问\(S[pl..pr]\)在\(T_ ...
- 【BZOJ3413】匹配(后缀自动机,线段树合并)
[BZOJ3413]匹配(后缀自动机,线段树合并) 题面 BZOJ 题解 很好的一道题目. 做一个转化,匹配的次数显然就是在可以匹配的区间中,每个前缀的出现次数之和. 首先是空前缀的出现次数,意味着你 ...
- 【CF666E】Forensic Examination 广义后缀自动机+倍增+线段树合并
[CF666E]Forensic Examination 题意:给你一个字符串s和一个字符串集合$\{t_i\}$.有q个询问,每次给出$l,r,p_l,p_r$,问$s[p_l,p_r]$在$t_l ...
- CF666E Forensic Examination 广义后缀自动机_线段树合并_树上倍增
题意: 给定一个串 $S$ 和若干个串 $T_{i}$每次询问 $S[pl..pr]$ 在 $Tl..Tr$ 中出现的最多次数,以及出现次数最多的那个串的编号. 数据范围: 需要离线 题解:首先,很常 ...
- 【Codeforces666E】Forensic Examination 后缀自动机 + 线段树合并
E. Forensic Examination time limit per test:6 seconds memory limit per test:768 megabytes input:stan ...
- CF 666E Forensic Examination 【SAM 倍增 线段树合并】
CF 666E Forensic Examination 题意: 给出一个串\(s\)和\(n\)个串\(t_i\),\(q\)次询问,每次询问串\(s\)的子串\(s[p_l:p_r]\)在串\(t ...
随机推荐
- 平衡二叉树之AVL树
AVL树(命名来源于作者姓名,Adelson-Velskii和Landis),即平衡二叉树,满足以下的条件: 1)它的左子树和右子树都是AVL树 2)左子树和右子树的高度差不能超过1 从条件1可能看出 ...
- 657. Judge Route Circle机器人能否返回
[抄题]: Initially, there is a Robot at position (0, 0). Given a sequence of its moves, judge if this r ...
- 关于HDFS默认block块大小
这是有疑惑的一个问题,因为在董西成的<Hadoop技术内幕--深入解析MapReduce架构设计与实现原理>中提到这个值是64M,而<Hadoop权威指南>中却说是128M,到 ...
- 13-Oulipo(kmp裸题)
http://acm.hdu.edu.cn/showproblem.php?pid=1686 Oulipo Time Limit: 3000/1000 MS (Java/Others) Memo ...
- python链接
http://www.crifan.com/files/doc/docbook/python_beginner_tutorial/release/html/python_beginner_tutori ...
- eclipse (android环境搭建)
如何安装java环境 http://jingyan.baidu.com/article/a24b33cd59b58e19fe002bb9.html eclipse安装教程 http://jingyan ...
- [GO]使用map生成 json
package main import ( "encoding/json" "fmt" ) func main() { m := make(map[) //因为 ...
- Ceph介绍
1. 介绍 云硬盘是IaaS云平台的重要组成部分,云硬盘给虚拟机提供了持久的块存储设备.目前的AWS 的EBS(Elastic Block store)给Amazon的EC2实例提供了高可用高可靠的块 ...
- redis的maxmemory设置以及淘汰策略介绍
转载地址:http://www.2cto.com/database/201507/420889.html redis的maxmemory参数用于控制redis可使用的最大内存容量.如果超过maxmem ...
- 编写高质量代码改善C#程序的157个建议——建议151:使用事件访问器替换公开的事件成员变量
建议151:使用事件访问器替换公开的事件成员变量 事件访问器包含两部分内容:添加访问器和删除访问器.如果涉及公开的事件字段,应该始终使用事件访问器.代码如下所示: class SampleClass ...