[CF666E]Forensic Examination:后缀自动机+线段树合并
分析
用到了两个小套路:
使用线段树合并维护广义后缀自动机的\(right\)集合。
查询\(S[L,R]\)在\(T\)中的出现次数:给\(T\)建SAM,在上面跑\(S\),跑到\(R\)的时候先判匹配长度是否\(\geq R-L+1\),如果是则跳parent使\(maxlen(x) \geq R-L+1\)的前提下\(maxlen(x)\)最小(这个过程有时需要倍增优化),这个点的\(|right(x)|\)就是所求。
然后这道题就没了(大概)。
代码
#include <bits/stdc++.h>
#define rin(i,a,b) for(register int i=(a);i<=(b);++i)
#define irin(i,a,b) for(register int i=(a);i>=(b);--i)
#define trav(i,a) for(register int i=head[a];i;i=e[i].nxt)
typedef long long LL;
using std::cin;
using std::cout;
using std::endl;
inline int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
return x*f;
}
const int MAXN=500005;
int n,m,q,las,tot;
int ecnt,head[MAXN<<1];
int sgt,root[MAXN<<1],lc[MAXN*40],rc[MAXN*40],maxp[MAXN*40],maxn[MAXN*40];
int loc,ql,qr,retp,retn;
int mpos[MAXN],mmatch[MAXN];
int anc[MAXN<<1][21];
char s[MAXN],str[MAXN];
struct sam{
int fa,to[26];
int len;
}a[MAXN<<1];
struct Edge{
int to,nxt;
}e[MAXN<<1];
inline void add_edge(int bg,int ed){
++ecnt;
e[ecnt].to=ed;
e[ecnt].nxt=head[bg];
head[bg]=ecnt;
}
#define mid ((l+r)>>1)
inline void pushup(int o){
if(maxn[lc[o]]>=maxn[rc[o]]){
maxp[o]=maxp[lc[o]];
maxn[o]=maxn[lc[o]];
}
else{
maxp[o]=maxp[rc[o]];
maxn[o]=maxn[rc[o]];
}
}
int upd(int pre,int l,int r){
int o=pre;
if(!o) o=++sgt;
if(l==r){
maxp[o]=l;
++maxn[o];
return o;
}
if(loc<=mid) lc[o]=upd(lc[pre],l,mid);
else rc[o]=upd(rc[pre],mid+1,r);
pushup(o);
return o;
}
void query(int o,int l,int r){
if(ql<=l&&r<=qr){
if(retn<maxn[o]){
retp=maxp[o];
retn=maxn[o];
}
return;
}
if(mid>=ql) query(lc[o],l,mid);
if(mid<qr) query(rc[o],mid+1,r);
}
int merge(int x,int y,int l,int r){
if(!x||!y) return x+y;
int o=++sgt;
if(l==r){
maxp[o]=l;
maxn[o]=maxn[x]+maxn[y];
return o;
}
lc[o]=merge(lc[x],lc[y],l,mid);
rc[o]=merge(rc[x],rc[y],mid+1,r);
pushup(o);
return o;
}
void write(int o,int l,int r){
if(l==r){
cout<<maxn[o]<<" ";
return;
}
write(lc[o],l,mid);
write(rc[o],mid+1,r);
}
#undef mid
void extend(int c,int idx){
int p=las,np=++tot;las=np;
a[np].len=a[p].len+1;
loc=idx;root[np]=upd(root[np],1,m);
while(p&&!a[p].to[c]){
a[p].to[c]=np;
p=a[p].fa;
}
if(!p){
a[np].fa=1;
return;
}
int q=a[p].to[c];
if(a[p].len+1==a[q].len){
a[np].fa=q;
return;
}
int nq=++tot;
a[nq]=a[q];
a[nq].len=a[p].len+1;
a[np].fa=a[q].fa=nq;
while(p&&a[p].to[c]==q){
a[p].to[c]=nq;
p=a[p].fa;
}
}
void dfs(int x){
trav(i,x){
int ver=e[i].to;
dfs(ver);
root[x]=merge(root[x],root[ver],1,m);
}
}
void match(){
int x=1,now=0;
rin(i,1,n){
while(x&&!a[x].to[s[i]]) x=a[x].fa,now=a[x].len;
if(!x){x=1,now=0;continue;}
x=a[x].to[s[i]],++now;
mpos[i]=x,mmatch[i]=now;
}
}
void buildanc(){
rin(i,1,tot) anc[i][0]=a[i].fa;
rin(i,1,20) rin(j,1,tot) anc[j][i]=anc[anc[j][i-1]][i-1];
}
inline int getanc(int x,int lim){
irin(i,20,0){
if(!anc[x][i]||a[anc[x][i]].len<lim) continue;
x=anc[x][i];
}
return x;
}
int main(){
scanf("%s",s+1);
n=strlen(s+1);
rin(i,1,n) s[i]-='a';
m=read();
tot=1;
rin(i,1,m){
scanf("%s",str+1);
int len=strlen(str+1);las=1;
rin(j,1,len) extend(str[j]-'a',i);
}
rin(i,2,tot) add_edge(a[i].fa,i);
dfs(1);buildanc();match();
q=read();
while(q--){
ql=read(),qr=read();int l=read(),r=read();
if(mmatch[r]<r-l+1){
printf("%d %d\n",ql,0);
continue;
}
int x=mpos[r],y=getanc(x,r-l+1);
retp=retn=0;query(root[y],1,m);
if(retp==0) printf("%d %d\n",ql,0);
else printf("%d %d\n",retp,retn);
}
return 0;
}
[CF666E]Forensic Examination:后缀自动机+线段树合并的更多相关文章
- 【Codeforces666E】Forensic Examination 后缀自动机 + 线段树合并
E. Forensic Examination time limit per test:6 seconds memory limit per test:768 megabytes input:stan ...
- cf666E. Forensic Examination(广义后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...
- BZOJ3413: 匹配(后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并... 首先可以转化一下模型(想不到qwq):问题可以转化为统计\(B\)中每个前缀在\(A\)中出现的次数.(画一画就出来了) 然后直 ...
- [Luogu5161]WD与数列(后缀数组/后缀自动机+线段树合并)
https://blog.csdn.net/WAautomaton/article/details/85057257 解法一:后缀数组 显然将原数组差分后答案就是所有不相交不相邻重复子串个数+n*(n ...
- 模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合)
模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合) Code: #include <bits/stdc++.h> using namespace std; #define ...
- 【BZOJ4556】[TJOI2016&HEOI2016] 字符串(后缀自动机+线段树合并+二分)
点此看题面 大致题意: 给你一个字符串\(s\),每次问你一个子串\(s[a..b]\)的所有子串和\(s[c..d]\)的最长公共前缀. 二分 首先我们可以发现一个简单性质,即要求最长公共前缀,则我 ...
- bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并)
bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并) bzoj Luogu 给出一个字符串 $ S $ 及 $ q $ 次询问,每次询问一个字符串 $ T $ ...
- CF666E Forensic Examination(后缀自动机+线段树合并)
给你一个串S以及一个字符串数组T[1..m],q次询问,每次问S的子串S[pl..pr]在T[l..r]中的哪个串里的出现次数最多,并输出出现次数. 如有多解输出最靠前的那一个. 我们首先对m个字符串 ...
- CF666E Forensic Examination [后缀自动机,线段树合并]
洛谷 Codeforces 思路 最初的想法:后缀数组+区间众数,似乎并不能过. 既然后缀数组不行,那就按照套路建出广义SAM,然后把\(S\)放在上面跑,得到以每个点结尾会到SAM上哪个节点. 询问 ...
随机推荐
- 入职一个月快速熟悉大型Vue项目经验感想
来到和睦的公司家庭已经一个月出头了,从技术层面来说,公司项目PC端是我目前来说接触的最大最复杂的项目了,德老师也说这个不断开发更新迭代的项目的代码量相对于全国的web来说是蛮多的,对于快速熟悉这样的大 ...
- c语言:<tchar.h>
头文件“<tchar.h>”作用就是为了进行ASCII码和UNICODE(wide-character)码的头文件(该头文件由微软提供): 这样我们就可以使用TCHAR.H头文件中的定义的 ...
- python-day9(正式学习)
目录 深浅拷贝 拷贝 浅拷贝 深拷贝 异常处理 什么是异常 语法错误 逻辑错误 异常的种类 常用的异常 其他异常 异常处理 提前预防 事后预防 抛出异常(基本没用) 断言(调试用,现在基本上没用) 文 ...
- redis 学习(11)-- redis pipeline
redis pipeline 什么是流水线(pipeline) 首先来看 redis 执行一次操作所需要的时间: 1 次时间 = 1 次网络时间 + 1次命令时间 执行 n 次就需要: n 次时间 = ...
- docker 配置私有仓库
1.使用docker 命令: 1.准备两台虚拟机,这里使用的是centos7,两台使用yum install docker 安装docker; 2.给两台虚拟机设置固定ip: 进入到虚拟机内 敲入命令 ...
- MySQL存储引擎知多少
MySQL是我们经常使用的数据库处理系统(DBMS),不知小伙伴们有没有注意过其中的“存储引擎”(storage_engine)呢?有时候面试题中也会问道MySQL几种常用的存储引擎的区别.这次就简短 ...
- Nginx编译参数详解
Nginx编译参数 1.当我们安装好nginx后,输入命令 nginx -V 可以看到nginx的编译参数信息,例如 如下图 2. 编译参数如下图 # Nginx安装目的目录或路径 --prefix ...
- 错误信息 NSError
一.获取系统的错误信息 比如移动文件时,获取文件操作错误: NSError *e = nil;[[NSFileManager defaultManager] moveItemAtPath:source ...
- CentOS7 SVN基本配置
开机自启指令如下 systemctl enable svnserve.service 对应可执行脚本文件路径 vim /etc/sysconfig/svnserve 查看状态: ps -ef|grep ...
- dedecms 列表 用分页标签 判断 当第一页则显示,第二页以上不显示 土办法!
arc.listview.class.php function GetPageListST($list_len,$listitem="index,end,pre,next,pageno,sp ...