Codeforces.666E.Forensic Examination(广义后缀自动机 线段树合并)
\(Description\)
给定串\(S\)和\(m\)个串\(T_i\)。\(Q\)次询问,每次询问\(l,r,p_l,p_r\),求\(S[p_l\sim p_r]\)在\(T_l\sim T_r\)中的哪个串出现次数最多,输出最多次数以及它是\(T\)中的第几个。若最多的有多个,输出下标最小的。
\(Solution\)
挺好的题吧
对\(T\)个串建SAM,然后要求出SAM每个节点上\(|right|\)最大的是哪个串。
每个节点的\(|right|\)可以在DFS parent树时合并子节点得到。如果用线段树,区间\(|right|\)最大的是哪个串也可以维护出来。
那么可以离线,在每个点处处理该点上的询问,边DFS边合并线段树得到所有答案。(当然可持久化一下在线也行?)
怎么得到\(S[p_l\sim p_r]\)在SAM上的匹配节点呢?
维护一个节点指针\(p\),拿\(S\)在SAM上尽可能匹配,匹配不了就跳\(fa\)。这样能保证当前\(S\)的后缀(\(S[i]\))一定在\(p\)节点出现了。
所以在\(i\)这里处理\(p_r=i\)的询问。只要我们从当前节点\(p\)一直跳\(fa\),就能找到\(S[p_l,p_r]\)所在的节点,其答案就是该节点的\(|right|\)状态。可以用倍增实现。
(就是从\(S[1,p_r]\)所匹配的节点\(p\)往上跳,跳到从上往下第一个\(len_x\geq p_r-p_l+1\)的节点\(x\),\(x\)就是\(S[p_l,p_r]\)所在的节点)
如果出现次数为\(0\)的话也要输出最靠前的(即\(l\))。=-=
唉 6点多写完代码调到现在 心累
//529ms 56700KB
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define gc() getchar()
#define Bit 16
const int N=5e5+5,M=5e4+5,S=M<<1;
int m,root[S],fa[S][18];
char s[N],tmp[M];
struct Edge
{
int Enum,H[N],nxt[N],to[N];
inline void AddEdge(int u,int v){
to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum;
}
}Pos,Qy;
struct Edge2
{
int Enum,H[S],nxt[S],to[S];
inline void AddEdge(int u,int v){
to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum;
}
}Par;
struct Queries{
int l,r,pl,pr;
}q[N];
struct Suffix_Automaton
{
int tot,las,fa[S],son[S][26],len[S];
Suffix_Automaton() {tot=las=1;}
void Insert(int c)
{
int np=++tot,p=las; len[las=np]=len[p]+1;
for(; p&&!son[p][c]; p=fa[p]) son[p][c]=np;
if(!p) fa[np]=1;
else
{
int q=son[p][c];
if(len[q]==len[p]+1) fa[np]=q;
else
{
int nq=++tot; len[nq]=len[p]+1;
memcpy(son[nq],son[q],sizeof son[q]);
fa[nq]=fa[q], fa[q]=fa[np]=nq;
for(; son[p][c]==q; p=fa[p]) son[p][c]=nq;
}
}
}
}sam;
struct Node
{
int val,id;
bool operator <(const Node &x)const{
return val<x.val||(val==x.val&&id>x.id);
}
}Ans[N];
struct Segment_Tree
{
#define S M*17
#define lson son[x][0]
#define rson son[x][1]
int tot,son[S][2];
Node node[S];
#undef S
#define Update(x) node[x]=std::max(node[lson],node[rson]);
void Insert(int &x,int l,int r,int pos)
{
x=++tot;
if(l==r) return (void)(node[x]=(Node){1,pos});
int m=l+r>>1;
pos<=m ? Insert(lson,l,m,pos) : Insert(rson,m+1,r,pos);
Update(x);//Update
}
int Merge(int x,int y)
{
if(!x||!y) return x^y;
if(!lson&&!rson) return node[x].val+=node[y].val, x;//叶节点,合并right
lson=Merge(lson,son[y][0]), rson=Merge(rson,son[y][1]);
Update(x); return x;
}
Node Query(int x,int l,int r,int L,int R)
{
if(!x) return (Node){0,L};
if(L<=l && r<=R) return node[x];
int m=l+r>>1;
if(L<=m)
if(m<R) return std::max(Query(lson,l,m,L,R),Query(rson,m+1,r,L,R));
else return Query(lson,l,m,L,R);
return Query(rson,m+1,r,L,R);
}
}T;
inline int read()
{
int now=0;register char c=gc();
for(;!isdigit(c);c=gc());
for(;isdigit(c);now=now*10+c-'0',c=gc());
return now;
}
void DFS(int x)
{
for(int i=Par.H[x]; i; i=Par.nxt[i])
DFS(Par.to[i]), root[x]=T.Merge(root[x],root[Par.to[i]]);
for(int i=Pos.H[x],id; i; i=Pos.nxt[i])
id=Pos.to[i], Ans[id]=T.Query(root[x],1,m,q[id].l,q[id].r);
}
int main()
{
scanf("%s",s+1); int n=strlen(s+1);
m=read();
for(int i=1; i<=m; ++i)
{
scanf("%s",tmp), sam.las=1;
for(int j=0,l=strlen(tmp); j<l; ++j)
sam.Insert(tmp[j]-'a'), T.Insert(root[sam.las],1,m,i);//不就是每位的|right[las]|=1吗→_→我在纠结什么
//就是las节点的线段树上,i位置的|right|=1,给i位置+1
}
int Q=read();
for(int i=1; i<=Q; ++i)
q[i]=(Queries){read(),read(),read(),read()}, Qy.AddEdge(q[i].pr,i);
int lim=sam.tot;
for(int x=2; x<=lim; ++x) Par.AddEdge(fa[x][0]=sam.fa[x],x);
for(int i=1; i<=Bit; ++i)
for(int x=2; x<=lim; ++x)
fa[x][i]=fa[fa[x][i-1]][i-1];
for(int c,now=0,p=1,i=1; i<=n; ++i)
{
if(sam.son[p][c=s[i]-'a']) p=sam.son[p][c], ++now;//!!!靠 这写错调了两个小时 唉 心累
else
{
for(c=s[i]-'a'; p&&!sam.son[p][c]; p=sam.fa[p]);
if(!p) {p=1, now=0; continue;}
now=sam.len[p]+1, p=sam.son[p][c];
}
for(int j=Qy.H[i],len,id; j; j=Qy.nxt[j])
{
id=Qy.to[j];
if(now<(len=q[id].pr-q[id].pl+1)) continue;
int x=p;
for(int i=Bit; ~i; --i)
if(sam.len[fa[x][i]]>=len) x=fa[x][i];
Pos.AddEdge(x,id);
}
}
DFS(1);
for(int i=1; i<=Q; ++i)
if(!Ans[i].val) printf("%d 0\n",q[i].l);
else printf("%d %d\n",Ans[i].id,Ans[i].val);
return 0;
}
Codeforces.666E.Forensic Examination(广义后缀自动机 线段树合并)的更多相关文章
- CF 666E Forensic Examination——广义后缀自动机+线段树合并
题目:http://codeforces.com/contest/666/problem/E 对模式串建广义后缀自动机,询问的时候把询问子串对应到广义后缀自动机的节点上,就处理了“区间”询问. 还要处 ...
- cf666E. Forensic Examination(广义后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...
- 【CF666E】Forensic Examination - 广义后缀自动机+线段树合并
广义SAM专题的最后一题了……呼 题意: 给出一个长度为$n$的串$S$和$m$个串$T_{1\cdots m}$,给出$q$个询问$l,r,pl,pr$,询问$S[pl\cdots pr]$在$T_ ...
- Codeforces 666E Forensic Examination SAM or SA+线段树合并
E. Forensic Examination http://codeforces.com/problemset/problem/666/E 题目大意:给模式串S以及m个特殊串,q个询问,询问S的子串 ...
- [CF666E]Forensic Examination:后缀自动机+线段树合并
分析 用到了两个小套路: 使用线段树合并维护广义后缀自动机的\(right\)集合. 查询\(S[L,R]\)在\(T\)中的出现次数:给\(T\)建SAM,在上面跑\(S\),跑到\(R\)的时候先 ...
- CF666E Forensic Examination(后缀自动机+线段树合并)
给你一个串S以及一个字符串数组T[1..m],q次询问,每次问S的子串S[pl..pr]在T[l..r]中的哪个串里的出现次数最多,并输出出现次数. 如有多解输出最靠前的那一个. 我们首先对m个字符串 ...
- Codeforces 666E Forensic Examination(广义后缀自动机+线段树合并)
将所有串(包括S)放一块建SAM.对于询问,倍增定位出该子串所在节点,然后要查询的就是该子串在区间内的哪个字符串出现最多.可以线段树合并求出该节点在每个字符串中的出现次数. #include<b ...
- Codeforces 666E Forensic Examination SAM+权值线段树
第一次做这种$SAM$带权值线段树合并的题 然而$zjq$神犇看完题一顿狂码就做出来了 $Orz$ 首先把所有串当成一个串建$SAM$ 我们对$SAM$上每个点 建一棵权值线段树 每个叶子节点表示一个 ...
- CF 666E Forensic Examination 【SAM 倍增 线段树合并】
CF 666E Forensic Examination 题意: 给出一个串\(s\)和\(n\)个串\(t_i\),\(q\)次询问,每次询问串\(s\)的子串\(s[p_l:p_r]\)在串\(t ...
随机推荐
- HTML&javaSkcript&CSS&jQuery&ajax(八)
一. <!DOCTYPE html><html><head><meta charset="utf-8"><tiitle> ...
- axure--轮播图
1.使用动态面板的循环实现图片轮播的要点:1)当鼠标移出动态面板的范围时才显示左右两边的方向按钮,否则该两个按钮都是隐藏的.则思路如下:且四个条件之间是“or”的关系,不是“and”[[Cursor. ...
- new/new[]和delete/delete[]是如何分配空间以及释放空间的
C++中程序存储空间除栈空间和静态区外,每个程序还拥有一个内存池,这部分内存被称为或堆(heap).程序可以用堆来存储动态分配的对象,即那些在程序运行时创建的对象.动态对象的生存期由程序来控制 ,当动 ...
- Play框架--初学笔记
目录结构 web_app 根目录 | sbt SBT Unix 批处理脚本用于启动sbt-launch.jar | sbt.bat SBT Windows 批处理脚本用于启动sbt-launch.ja ...
- 步步为营-86-WSFUpload组件
文件上传组件,所需js文件和图片在百度网盘对应的文件夹下 <%@ Page Language="C#" AutoEventWireup="true" Co ...
- 基于容器的ETCD集群脚本
其实是从上一篇的脚本里剥离出来的. 加深一下印象吧. docker run \ -d \ -p ${ETCD_CLI_PORT}:${ETCD_CLI_PORT} \ -p ${ETCD_CLU_PO ...
- [转] 跨域资源共享 CORS 详解
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing). 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从 ...
- 【Socket】Java Socket编程基础及深入讲解
Socket是Java网络编程的基础,了解还是有好处的, 这篇文章主要讲解Socket的基础编程.Socket用在哪呢,主要用在进程间,网络间通信.本篇比较长,特别做了个目录: 一.Socket通信基 ...
- .NET Framework反射总结
概述 程序集的反射以及动态的创建类对象,是自动化编程常用的到知识原理,比如插件编程.模板设计模式,都可以采用发射机制动态的去创建实例化对象,实现类的动态加载.这里简单总结下,常用到的Framework ...
- 10个财务工作中常用的 Excel 万能公式
1.多条件判断公式 =IF(AND(条件1,条件2...条件n),同时满足条件返回的值,不满足条件返回的值) =IF(OR(条件1,条件2...条件n),同时满足任一条件返回的值,不满足条件返回的值) ...