CF666E Forensic Examination SAM+倍增,线段树和并
题面:
给你一个串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+倍增,线段树和并的更多相关文章
- CF666E Forensic Examination——SAM+线段树合并+倍增
RemoteJudge 题目大意 给你一个串\(S\)以及一个字符串数组\(T[1...m]\),\(q\)次询问,每次问\(S\)的子串\(S[p_l...p_r]\)在\(T[l...r]\)中的 ...
- CF666E Forensic Examination SAM+线段树合并+前缀树倍增
$ \color{#0066ff}{ 题目描述 }$ 给你一个串\(S\)以及一个字符串数组\(T[1..m]\),\(q\)次询问,每次问\(S\)的子串\(S[p_l..p_r]\)在\(T[l. ...
- CF 666E Forensic Examination 【SAM 倍增 线段树合并】
CF 666E Forensic Examination 题意: 给出一个串\(s\)和\(n\)个串\(t_i\),\(q\)次询问,每次询问串\(s\)的子串\(s[p_l:p_r]\)在串\(t ...
- 【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 ...
- LOJ 北校门外的回忆 倍增+线段树
正解:倍增+线段树 解题报告: 传送门! $umm$这题有个对正解毫无启发的部分分还有个正解,都挺神仙的所以我都写了趴$QAQ$ 先说部分分 可以考虑把$x$向$x+lowbit(x)$连边,然后当$ ...
- CF666E Forensic Examination 广义SAM、线段树合并、倍增、扫描线
传送门 朴素想法:对\(M\)个匹配串\(T_1,...,T_M\)建立广义SAM,对于每一次询问,找到这个SAM上\(S[pl...pr]\)对应的状态,然后计算出对于每一个\(i \in [l,r ...
- 【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$ 中出现的最多次数,以及出现次数最多的那个串的编号. 数据范围: 需要离线 题解:首先,很常 ...
随机推荐
- 洛谷 - P2335 - 位图 - 简单dp
https://www.luogu.org/problemnew/show/P2335 假如我们使用dp的话,每次求出一个点的左上方.右上方.左下方.右下方的最近的白点的距离.那么只是n²的复杂度.这 ...
- 洛谷 - P1044 - 栈 - 简单dp
https://www.luogu.org/problemnew/show/P1044 由于是用标签搜索进来的,所以这道题一定是有dp的解法. 很显然规定每次加入元素之前可以从栈中清理出任意数量的元素 ...
- bzoj 1060: [ZJOI2007]时态同步【树形dp】
可能算不上dp,大概是个树形模拟 先一遍dfs算出f[u]为每个点最深的叶子到u的距离,然后再dfs一下,ans加上f[u]-f[e[i].to]-e[i].va,f[u]-f[e[i].to]是这条 ...
- C++结构体的应用_YCOJ
结构体是一种自定义的东西,用struct来定义.在他里面, 可以装许多东西,比如int,string,char,bool等等等等. 如: struct a{ string name; int a; i ...
- expect实现配置机器信任关系
利用expect的交互功能,自动配置信任机器之间的信任关系. 代码里会判断机器是否生成了秘钥,如果没有生成过,则自动帮助你执行 ssh-keygen #!/bin/sh expect_ssh_copy ...
- 第十一篇 .NET高级技术之内置泛型委托
Func.Action 一.如果不是声明为泛型委托 委托的类型名称不能重载,也就是不能名字相同类型参数不同 二..Net中内置两个泛型委托Func.Action(在“对象浏览器”的mscorlib的S ...
- PHP获取今天开始和结束的时间戳、每周开始结束的时间戳、每月开始结束的时间戳
PHP获取今天内的时间 今天开始和结束的时间戳 $t = time(); $start = mktime(0,0,0,date("m",$t),date("d" ...
- java基本数据类型在栈中怎么存放的?
参考地址:https://www.zhihu.com/question/24747160 问:int a = 3; 首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟 ...
- Varnish快速安装及测试
实验环境: slave-147: 192.168.75.147 slave-148: 192.168.75.148 两台机器均已关闭selinux,关闭iptables. varnish部署 ...
- CGI和Servlet的比较
转载自:http://www.maxhis.info/java/cgi-vs-servlet/ 概括来说,CGI和Servlet可以完成相同的功能. CGI(Common Gateway Interf ...