题面:

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

分析:

  第一次见这道题时,由于对算法十分陌生,就将其压进了任务计划,这一天又提到了这道题,这才算是重见天日。

  数据结构题,越来越注重多种数据结构的搭配使用。搭配使用呢,我们就要知道各种数据结构的功能与适用范围,下面是这道题一个理想的思考过程(虽然我是抄的题解……)

  首先,一个模式串的区间要在多个串上进行匹配,我们可以想到多串构造的广义后缀自动机。

  由于我们要对一个模式串的区间,询问:在一个区间的匹配串中,最多出现在哪个匹配串,以及出现了多少次。(有点拗口)

  我们需要使用一个支持区间查询的数据结构,并且,我们需要支持在SAM上,查询一个点的right集合中的所有点的信息。

  我们采取的策略是,对于SAM的每一个点,我们建一棵值域为1~m的权值线段树,然后离线处理询问,将询问挂链(开vector存下来应该也可以),然后自底向上线段树合并,回答询问。

  但是,我们对询问应该怎样调整一下呢,总不能对于每个区间都在广义SAM上跑一遍吧~

  我们要总地把模式串S在SAM上跑一遍,只需要对每个询问记录右端点qr,当S匹配到这个点的时候,将所有右端点为qr的询问挂到链上,(为什么不直接挂链呢,因为我们不能随便挂链,需要挂到SAM上最早包含模式串这一段的那个点上(也就是匹配到当前点时,parent树上最远的一个len大于这一段长度的点上)),这里言语很难解释,只有自己体会这样做的妙处。但是这步操作怎么做呢,暴跳的话理论复杂度是过不去的(但实际上过得去),所以我们最好用一个倍增数组来做这个。(万一被hack掉可就不好了)

  然后这道题跑线段树合并即可,由于是抄的题解代码,所以代码风格不太对(除了压行风格)请理智吸收。

代码:

 #include<bits/stdc++.h>
using namespace std;
const int N=;
struct SAM{
struct node{
int son[],fa,l;
}t[N];int lst,cnt;
void init(){lst=cnt=;}
void ins(int c){
int p=lst,np=++cnt;lst=np;
t[np].l=t[p].l+;
for(;p&&!t[p].son[c];p=t[p].fa)
t[p].son[c]=np;if(!p) t[np].fa=;
else{
int q=t[p].son[c];
if(t[q].l==t[p].l+) t[np].fa=q;
else{
int nq=++cnt;t[nq]=t[q];
t[nq].l=t[p].l+;
t[q].fa=t[np].fa=nq;
while(p&&t[p].son[c]==q)
t[p].son[c]=nq,p=t[p].fa;
}
}
}
}sam;int n,m,f[N][];
struct data{int v,p;}ans[N];
bool operator<(data a,data b){
return (a.v<b.v)||(a.v==b.v&&a.p>b.p);
} struct segtree{int ls,rs;data v;}t[N<<];
struct que{int l,r,pl,pr;}q[N];
int tot,rt[N];char S[N],T[N];
void update(int &x,int l,int r,int p){
if(!x) x=++tot;if(l==r)
{t[x].v.v++;t[x].v.p=p;return;}
int mid=l+r>>;
if(p<=mid) update(t[x].ls,l,mid,p);
else update(t[x].rs,mid+,r,p);
t[x].v=max(t[t[x].ls].v,t[t[x].rs].v);
} int merge(int x,int y){
if(!y||!x) return x|y;
if(!t[x].ls&&!t[x].rs)
{t[x].v.v+=t[y].v.v;return x;}
t[x].ls=merge(t[x].ls,t[y].ls);
t[x].rs=merge(t[x].rs,t[y].rs);
t[x].v=max(t[t[x].ls].v,t[t[x].rs].v);
return x;
} data query(int x,int l,int r,int L,int R){
if(L<=l&&r<=R) return t[x].v;
int mid=l+r>>;if(R<=mid)
return query(t[x].ls,l,mid,L,R);
if(L>mid) return query(t[x].rs,mid+,r,L,R);
return max(query(t[x].ls,l,mid,L,R),
query(t[x].rs,mid+,r,L,R));
} struct link{
struct line{int y,nxt;}e[N];
int h[N],c;void add(int x,int y)
{e[++c]=(line){y,h[x]};h[x]=c;}
}par,qy,aw;void dfs(int x){
for(int i=par.h[x];i;i=par.e[i].nxt)
dfs(par.e[i].y),rt[x]=merge(rt[x],rt[par.e[i].y]);
for(int i=aw.h[x];i;i=aw.e[i].nxt)
ans[aw.e[i].y]=query(rt[x],,m,q[aw.e[i].y].l,
q[aw.e[i].y].r);return ;
} int main(){
scanf("%s",S+);n=strlen(S+);
scanf("%d",&m);sam.init();
for(int i=;i<=m;i++){
sam.lst=;scanf("%s",T+);
for(int j=,l=strlen(T+);j<=l;j++)
sam.ins(T[j]-),update(rt[sam.lst],,m,i);
} int Q;scanf("%d",&Q);
for(int i=,l,r,pl,pr;i<=Q;i++){
scanf("%d%d%d%d",&l,&r,&pl,&pr);
q[i]=(que){l,r,pl,pr};qy.add(q[i].pr,i);
} for(int i=;i<=sam.cnt;i++)
par.add(f[i][]=sam.t[i].fa,i);
for(int i=;i<;i++)
for(int j=;j<=sam.cnt;j++)
f[j][i]=f[f[j][i-]][i-];
for(int i=,nw=,len=;i<=n;i++){
int c=S[i]-;
while(nw&&!sam.t[nw].son[c])
nw=sam.t[nw].fa,len=sam.t[nw].l;
if(!nw){nw=,len=;continue;}
nw=sam.t[nw].son[c];len+=;
for(int j=qy.h[i];j;j=qy.e[j].nxt){
int y=qy.e[j].y,x=nw,tmp;
tmp=q[y].pr-q[y].pl+;
if(len<tmp) continue;
for(int k=;~k;k--)
if(sam.t[f[x][k]].l>=tmp) x=f[x][k];
aw.add(x,y);
}
} dfs();
for(int i=;i<=Q;i++){
if(!ans[i].v) ans[i].p=q[i].l;
printf("%d %d\n",ans[i].p,ans[i].v);
} return ;
}

理论上过得去但跑的慢

 //这是CF666E理论上过不去(但实际上过得去)的代码
#include<bits/stdc++.h>
using namespace std;
const int N=;
struct SAM{
struct node{
int son[],fa,l;
}t[N];int lst,cnt;
void init(){lst=cnt=;}
void ins(int c){
int p=lst,np=++cnt;lst=np;
t[np].l=t[p].l+;
for(;p&&!t[p].son[c];p=t[p].fa)
t[p].son[c]=np;if(!p) t[np].fa=;
else{
int q=t[p].son[c];
if(t[q].l==t[p].l+) t[np].fa=q;
else{
int nq=++cnt;t[nq]=t[q];
t[nq].l=t[p].l+;
t[q].fa=t[np].fa=nq;
while(p&&t[p].son[c]==q)
t[p].son[c]=nq,p=t[p].fa;
}
}
}
}sam;int n,m,f[N][];
struct data{int v,p;}ans[N];
bool operator<(data a,data b){
return (a.v<b.v)||(a.v==b.v&&a.p>b.p);
} struct segtree{int ls,rs;data v;}t[N<<];
struct que{int l,r,pl,pr;}q[N];
int tot,rt[N];char S[N],T[N];
void update(int &x,int l,int r,int p){
if(!x) x=++tot;if(l==r)
{t[x].v.v++;t[x].v.p=p;return;}
int mid=l+r>>;
if(p<=mid) update(t[x].ls,l,mid,p);
else update(t[x].rs,mid+,r,p);
t[x].v=max(t[t[x].ls].v,t[t[x].rs].v);
} int merge(int x,int y){
if(!y||!x) return x|y;
if(!t[x].ls&&!t[x].rs)
{t[x].v.v+=t[y].v.v;return x;}
t[x].ls=merge(t[x].ls,t[y].ls);
t[x].rs=merge(t[x].rs,t[y].rs);
t[x].v=max(t[t[x].ls].v,t[t[x].rs].v);
return x;
} data query(int x,int l,int r,int L,int R){
if(L<=l&&r<=R) return t[x].v;
int mid=l+r>>;if(R<=mid)
return query(t[x].ls,l,mid,L,R);
if(L>mid) return query(t[x].rs,mid+,r,L,R);
return max(query(t[x].ls,l,mid,L,R),
query(t[x].rs,mid+,r,L,R));
} struct link{
struct line{int y,nxt;}e[N];
int h[N],c;void add(int x,int y)
{e[++c]=(line){y,h[x]};h[x]=c;}
}par,qy,aw;void dfs(int x){
for(int i=par.h[x];i;i=par.e[i].nxt)
dfs(par.e[i].y),rt[x]=merge(rt[x],rt[par.e[i].y]);
for(int i=aw.h[x];i;i=aw.e[i].nxt)
ans[aw.e[i].y]=query(rt[x],,m,q[aw.e[i].y].l,
q[aw.e[i].y].r);return ;
} int main(){
scanf("%s",S+);n=strlen(S+);
scanf("%d",&m);sam.init();
for(int i=;i<=m;i++){
sam.lst=;scanf("%s",T+);
for(int j=,l=strlen(T+);j<=l;j++)
sam.ins(T[j]-),update(rt[sam.lst],,m,i);
} int Q;scanf("%d",&Q);
for(int i=,l,r,pl,pr;i<=Q;i++){
scanf("%d%d%d%d",&l,&r,&pl,&pr);
q[i]=(que){l,r,pl,pr};qy.add(q[i].pr,i);
} for(int i=;i<=sam.cnt;i++)
par.add(f[i][]=sam.t[i].fa,i);
for(int i=,nw=,len=;i<=n;i++){
int c=S[i]-;
while(nw&&!sam.t[nw].son[c])
nw=sam.t[nw].fa,len=sam.t[nw].l;
if(!nw){nw=,len=;continue;}
nw=sam.t[nw].son[c];len+=;
for(int j=qy.h[i];j;j=qy.e[j].nxt){
int y=qy.e[j].y,x=nw,tmp;
tmp=q[y].pr-q[y].pl+;
if(len<tmp) continue;
while(sam.t[f[x][]].l>=tmp) x=f[x][];
aw.add(x,y);
}
} dfs();
for(int i=;i<=Q;i++){
if(!ans[i].v) ans[i].p=q[i].l;
printf("%d %d\n",ans[i].p,ans[i].v);
} return ;
}

理论上过不去但跑得更快

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+线段树合并+前缀树倍增

    $ \color{#0066ff}{ 题目描述 }$ 给你一个串\(S\)以及一个字符串数组\(T[1..m]\),\(q\)次询问,每次问\(S\)的子串\(S[p_l..p_r]\)在\(T[l. ...

  3. CF 666E Forensic Examination 【SAM 倍增 线段树合并】

    CF 666E Forensic Examination 题意: 给出一个串\(s\)和\(n\)个串\(t_i\),\(q\)次询问,每次询问串\(s\)的子串\(s[p_l:p_r]\)在串\(t ...

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

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

  5. 【codeforces666E】Forensic Examination 广义后缀自动机+树上倍增+线段树合并

    题目描述 给出 $S$ 串和 $m$ 个 $T_i$ 串,$q$ 次询问,每次询问给出 $l$ .$r$ .$x$ .$y$ ,求 $S_{x...y}$ 在 $T_l,T_{l+1},...,T_r ...

  6. LOJ 北校门外的回忆 倍增+线段树

    正解:倍增+线段树 解题报告: 传送门! $umm$这题有个对正解毫无启发的部分分还有个正解,都挺神仙的所以我都写了趴$QAQ$ 先说部分分 可以考虑把$x$向$x+lowbit(x)$连边,然后当$ ...

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

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

  8. 【CF666E】Forensic Examination 广义后缀自动机+倍增+线段树合并

    [CF666E]Forensic Examination 题意:给你一个字符串s和一个字符串集合$\{t_i\}$.有q个询问,每次给出$l,r,p_l,p_r$,问$s[p_l,p_r]$在$t_l ...

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

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

随机推荐

  1. 如何优雅地停止Java进程

    目录 理解停止Java进程的本质 应该如何正确地停止Java进程 如何注册关闭钩子 使用关闭钩子的注意事项 信号量机制 总结 理解停止Java进程的本质 我们知道,Java程序的运行需要一个运行时环境 ...

  2. hihocoder 1584 Bounce(找规律)

    传送门 题意 略 分析 我们观察几张图 发现菱形的边长为n-1和m-1的公约数 将图简化一下 接下来我们计算只经过一次的点,分成两类 1.与边相交 num1=x+y 2.未与边相交,在菱形内 num2 ...

  3. poj3617【贪心】

    题意: 给定长度为N的字符串S,要构造一个长度为N的字符串T串. 从S的头部删除一个字符,加到T的尾部 从S的尾部删除一个字符,加到T的尾部 目标是构造字典序尽可能小的字符串. 思路: 贪心,每次取小 ...

  4. HDOJ1584蜘蛛牌【DFS】

    10张牌,大的只能跟小的跑,可以针对每一个状态进行搜索,求一个最小的移动距离. 但是不会怎么遍历整个状态是硬伤? 因为只能大的跟着小的. 先把小的标记,去寻找大的点,最终一定是满足的吧. 比如先标记1 ...

  5. hdoj1176【DP】

    DP基础吧.A掉还是挺爽的.就是考虑在两端只能是从前一秒的内部一米或原来的点来进行,但是在5秒以内可到达点是逐渐外扩的,并不是[0,10],所以就特殊考虑了一下.后面两端是0和10,中间的点可以从上一 ...

  6. Hibernate中的Query对象查询所有记录

    映射文件,核心文件,实体类,工具类的内容都不变直接看测试方法中的代码: package com.yinfu.test; import java.util.List; import org.hibern ...

  7. 使用pytesseract识别验证码,报错WindowsError: [Error 2]

    问题现象: 按照网上的方式进行代码编写,使用pytesseract模块,然后导入指定图片进行解析,报错WindowsError: [Error 2] 问题原因: 源代码里面的路径设置错误,这里有一个坑 ...

  8. deque双向队列

    对于双向队列,与队列queue以及vector容器的区别就在于,名字不同,也就是它是双向的,可以从头开始操作,也可以从末尾开始操作. 双向队列的常用方法跟队列queue差不多: 头文件: #inclu ...

  9. 使用ansible对远程主机上的ssh公钥进行批量分发

    使用ansible对远程主机上的ssh公钥进行批量分发或者是删除修改操作 ansible内置了一个authorized_key模块,这个模块很好用,我们使用这个模块可以对远程 主机上的ssh公钥进行批 ...

  10. 18.3.2从Class上获取信息(内部类接口等)

    内部类 接口.枚举.注释类型