题面:

  给你一个串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. React 从入门到进阶之路(八)

    之前的文章我们介绍了 React中的组件.父子组件.React props父组件给子组件传值.子组件给父组件传值.父组件中通过refs获取子组件属性和方法.接下来我们将介绍 React propTyp ...

  2. Gradle技术之四 - Gradle的Task详解

    1 Gradle的Task详解 1 Task定义和配置 2 Task的执行 3 Task的依赖和执行顺序 4 Task类型 5 Task结合gradle的生命周期 6 Task实战 1.1 Task定 ...

  3. J20170403-gg

    うっすら 微微的,薄薄的 グラデーション 渐变 ぼかし(暈し) 晕色 由浓到淡渐变上色的东西 シャドウ 影子,阴影 ドメイン 域名  サブドメイン 子域名

  4. J20170422-hm

    ワイルドスクリプト wild script 通配符 シェルスクリプト     shell脚本

  5. BZOJ1026(数位dp)

    数位dp第二道-就当成搜索,套板子写了写.我写的dp[pos][pre][state0]记录的是当前pos位没有限制时.前面的数是pre时.前面是否都是0时的方案数. #include <cst ...

  6. 洛谷1736(二维dp+预处理)

    洛谷1387的进阶版,但很像. 1387要求是“全为1的正方形”,取dp[i][j] = min(dp[i-1][j-1], min(dp[i-1][j], dp[i][j-1]))吧?这个有“只有对 ...

  7. 浅析String

    浅析String String的设计结构: 首先我们看一下 String的源码 public final class String     implements java.io.Serializabl ...

  8. JavaScript入门2

    5.document对象:Document对象是window对象的一个对象属性,代表浏览器窗口中装载的整个HTML文档.文档中的每个HTML元素对应着JavaScript对象. 因为document代 ...

  9. [已读]响应式web设计

    去年冲着响应式这三个字买的,很快就读完了,因为说实话都挺浅显的内容.真正涉及到响应式的是第二和第三章(媒体查询 em 百分比图片),其他的h5与css3关系不大.

  10. 初识node,原理与浏览器何其相似

    话不多说,直接上图. 今日入手开始学习Nodejs,加油吧,小小前端的大V梦ヾ(◍°∇°◍)ノ゙