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 ...
随机推荐
- ssh框架,工具类调用service层方法
解决方法: @Component//声明为spring组件 public class CopyFileUtil{ @Autowired private DataFileManager dataFile ...
- MenuItem属性
[MenuItem属性] The MenuItem attribute allows you to add menu items to the main menu. The MenuItem attr ...
- JAVA中List的几个方法
add()方法.插入某个位置的数据,他有两个参数一个参数是下标,一个参数是元素.需要注意的是下标大小应该小于等于List集合大小,否则就会抛出下标越界异常! 代码: public static ...
- 《DNA比对》蓝桥杯复赛试题
题目描述 脱氧核糖核酸即常说的DNA,是一类带有遗传信息的生物大分子.它由4种主要的脱氧核苷酸(dAMP.dGMP.dCMT和dTMP)通过磷酸二酯键连接而成.这4种核苷酸可以分别记为:A.G.C.T ...
- 381. Insert Delete GetRandom O(1) - Duplicates allowed允许重复的设计1数据结构
[抄题]: Design a data structure that supports all following operations in average O(1) time. Note: Dup ...
- LIS和LCS LCIS
首先介绍一下LIS和LCS的DP解法O(N^2) LCS:两个有序序列a和b,求他们公共子序列的最大长度 我们定义一个数组DP[i][j],表示的是a的前i项和b的前j项的最大公共子序列的长度,那么由 ...
- Linux ls命令详解-乾颐堂CCIE
ls命令用法举例: 例一:列出/home文件夹下的所有文件和目录的详细资料: 1 ls -l -R /home 命令参数之前要有一短横线“-”, 上面的命令也可以这样写: 1 ls -lR /ho ...
- HBase表的memstore与集群memstore
一直有一个问题,今天调查了一下源码算是明白了. ===问题=== 通过java api(如下代码所示)在创建表的时候,可以通过setMemStoreFlushSize函数来指定memstore的大小, ...
- Hibernate环境搭建
Hibernate的环境搭建,主要步骤分为一下四步: 首先创建一个工程,在工程里创建一个实体类User,在这个实体类中必须包含无参的构造器,和这个类对属性的存取方法(getter and setter ...
- bootstrap小图标引用方法
<span class="glyphicon glyphicon-search"></span> <span class="glyphico ...