【CF666E】Forensic Examination - 广义后缀自动机+线段树合并
广义SAM专题的最后一题了……呼
题意:
给出一个长度为$n$的串$S$和$m$个串$T_{1\cdots m}$,给出$q$个询问$l,r,pl,pr$,询问$S[pl\cdots pr]$在$T_l\cdots T_r$中哪个串出现次数最多,出现了多少次。
$1\leq n,q\leq 10^5,1\leq m,\sum|T|\leq 10^4$
串中只会出现小写字母
题解:
神题啊……放图镇楼
先对T串建出广义SAM,然后把S放到上面匹配,求出每个字符所代表的节点,那么每次查询就相当于求这一段字符在SAM上对应的节点的right集合包含的字符串的众数是哪个串,显然这是parent树上的一个子树众数问题;
考虑如何维护right集合在所有$T$中的出现次数,可以对每一个节点开一棵线段树,维护每个T串的出现次数的最大值,这样子在parent树上从下往上线段树合并即可求出right集合;
把询问离线按照右端点排序,把询问标记打在parent树上,最后dfs一遍合并+处理询问即可;
口胡起来不难但是写起来……超爽!
代码:
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#define inf 2147483647
#define eps 1e-9
using namespace std;
typedef long long ll;
typedef double db;
struct task{
int v,id;
task(){v=id=;}
friend bool operator <(task a,task b){
return a.v==b.v?a.id>b.id:a.v<b.v;
}
}ans[];
struct qu{
int l,r,ql,qr;
}q[];
struct edge{
int v,next;
}a[];
struct node{
int ls,rs;
task v;
}t[];
int n,m,Q,len,ch,nw=,tote=,tot=,rt=,cnt=,last,head[],rts[],son[][],fa[],mx[],f[][];
vector<int>qrs[];
vector<int>as[];
char st[],tt[];
void add(int u,int v){
a[++tote].v=v;
a[tote].next=head[u];
head[u]=tote;
}
void updata(int &u,int l,int r,int x){
if(!u)u=++cnt;
if(l==r){
t[u].v.v++;
t[u].v.id=x;
return;
}
int mid=(l+r)/;
if(x<=mid)updata(t[u].ls,l,mid,x);
else updata(t[u].rs,mid+,r,x);
t[u].v=max(t[t[u].ls].v,t[t[u].rs].v);
}
void merge(int &x,int y){
if(!x||!y){
x|=y;
return;
}
if(!t[x].ls&&!t[x].rs){
t[x].v.v+=t[y].v.v;
return;
}
merge(t[x].ls,t[y].ls);
merge(t[x].rs,t[y].rs);
t[x].v=max(t[t[x].ls].v,t[t[x].rs].v);
}
task query(int u,int l,int r,int L,int R){
if(L<=l&&r<=R){
return t[u].v;
}
int mid=(l+r)/;
task ret;
if(L<=mid)ret=max(ret,query(t[u].ls,l,mid,L,R));
if(mid<R)ret=max(ret,query(t[u].rs,mid+,r,L,R));
return ret;
}
void extend(int ch){
int p=last,np=++tot;
mx[np]=mx[p]+;
for(;p&&!son[p][ch];p=fa[p])son[p][ch]=np;
if(!p)fa[np]=rt;
else{
int q=son[p][ch];
if(mx[q]==mx[p]+)fa[np]=q;
else{
int nq=++tot;
mx[nq]=mx[p]+;
memcpy(son[nq],son[q],sizeof(son[q]));
fa[nq]=fa[q];
fa[q]=fa[np]=nq;
for(;p&&son[p][ch]==q;p=fa[p])son[p][ch]=nq;
}
}
last=np;
}
void dfs(int u){
for(int tmp=head[u];tmp!=-;tmp=a[tmp].next){
int v=a[tmp].v;
dfs(v);
merge(rts[u],rts[v]);
}
for(int i=,ii=as[u].size();i<ii;i++){
ans[as[u][i]]=query(rts[u],,m,q[as[u][i]].l,q[as[u][i]].r);
}
}
int main(){
memset(head,-,sizeof(head));
scanf("%s%d",st+,&m);
n=strlen(st+);
for(int i=;i<=m;i++){
scanf("%s",tt);
len=strlen(tt);
last=rt;
for(int j=;j<len;j++){
extend(tt[j]-'a');
updata(rts[last],,m,i);
}
}
scanf("%d",&Q);
for(int i=;i<=Q;i++){
scanf("%d%d%d%d",&q[i].l,&q[i].r,&q[i].ql,&q[i].qr);
qrs[q[i].qr].push_back(i);
}
for(int i=;i<=tot;i++){
f[i][]=fa[i];
add(fa[i],i);
}
for(int j=;j<=;j++){
for(int i=;i<=tot;i++){
f[i][j]=f[f[i][j-]][j-];
}
}
len=;
for(int i=;i<=n;i++){
ch=st[i]-'a';
for(;nw&&!son[nw][ch];)nw=fa[nw],len=mx[nw];
if(!nw){
nw=rt;
len=;
}else{
nw=son[nw][ch];
len++;
for(int j=,jj=qrs[i].size();j<jj;j++){
int v=qrs[i][j];
if(len>=q[v].qr-q[v].ql+){
int _nw=nw;
for(int k=;k>=;k--){
if(mx[f[_nw][k]]>=q[v].qr-q[v].ql+){
_nw=f[_nw][k];
}
}
as[_nw].push_back(v);
}
}
}
}
dfs();
for(int i=;i<=Q;i++){
if(!ans[i].v)ans[i].id=q[i].l;
printf("%d %d\n",ans[i].id,ans[i].v);
}
return ;
}
【CF666E】Forensic Examination - 广义后缀自动机+线段树合并的更多相关文章
- cf666E. Forensic Examination(广义后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...
- CF 666E Forensic Examination——广义后缀自动机+线段树合并
题目:http://codeforces.com/contest/666/problem/E 对模式串建广义后缀自动机,询问的时候把询问子串对应到广义后缀自动机的节点上,就处理了“区间”询问. 还要处 ...
- [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(广义后缀自动机 线段树合并)
题目链接 \(Description\) 给定串\(S\)和\(m\)个串\(T_i\).\(Q\)次询问,每次询问\(l,r,p_l,p_r\),求\(S[p_l\sim p_r]\)在\(T_l\ ...
- CF666E Forensic Examination 广义SAM、线段树合并、倍增、扫描线
传送门 朴素想法:对\(M\)个匹配串\(T_1,...,T_M\)建立广义SAM,对于每一次询问,找到这个SAM上\(S[pl...pr]\)对应的状态,然后计算出对于每一个\(i \in [l,r ...
- CF666E Forensic Examination 广义后缀自动机_线段树合并_树上倍增
题意: 给定一个串 $S$ 和若干个串 $T_{i}$每次询问 $S[pl..pr]$ 在 $Tl..Tr$ 中出现的最多次数,以及出现次数最多的那个串的编号. 数据范围: 需要离线 题解:首先,很常 ...
- Codeforces 666E Forensic Examination(广义后缀自动机+线段树合并)
将所有串(包括S)放一块建SAM.对于询问,倍增定位出该子串所在节点,然后要查询的就是该子串在区间内的哪个字符串出现最多.可以线段树合并求出该节点在每个字符串中的出现次数. #include<b ...
- BZOJ3413: 匹配(后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并... 首先可以转化一下模型(想不到qwq):问题可以转化为统计\(B\)中每个前缀在\(A\)中出现的次数.(画一画就出来了) 然后直 ...
随机推荐
- OSX: 安装打印机的有用命令行
事实上非常easy.就是有用lpadmin命令,以下给出一个样例: printername="YOUR_PRINTER_NAME" location="LOCATION ...
- print puts p 用法
print puts p 用法 print "a\n" puts "a" p "a\n" 输出为 a a "a\n" p ...
- 关于DM8168中移植算法速度慢、效率低的新发现
有不少的朋友,特别是刚刚接触DSP的朋友.基于DVRRDK编写C代码发现执行速度特别慢,我在上面简单的对每一个像素的UV分量赋值=0x80,这样就成了灰度图像.对1080P图像进行操作,发现处理每帧要 ...
- Chrome Extension 的 webRequest模块的解读
Chrome Extension 的 webRequest模块的解读 文档在此:http://developer.chrome.com/trunk/extensions/webRequest.ht ...
- Linux命令(二)——目录和文件管理命令
一.Linux系统的目录结构 1.根目录(/):顶层目录,某些系统中的唯一分区. 2./bin命令文件目录:包含Linux命令的二进制可执行文件. 3./boot目录:存放系统的内核文件和引导装载程序 ...
- DeepLearning to digit recognizer in kaggle
DeepLearning to digit recongnizer in kaggle 近期在看deeplearning,于是就找了kaggle上字符识别进行练习.这里我主要用两种工具箱进行求解.并比 ...
- CSU 1506 Problem D: Double Shortest Paths(最小费用最大流)
题意:2个人从1走到n,假设一条路第一次走则是价值di,假设第二次还走这条路则须要价值di+ai,要你输出2个人到达终点的最小价值! 太水了!一条边建2次就OK了.第一次价值为di,第二次为ai+di ...
- hdoj--1864--最大保险额(背包)
最大报销额 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Subm ...
- 【撸码caffe 一】syncedmen.hpp
SyncedMemory类主要负责在主机(CPU)和设备(GPU)之间管理内存分配和数据同步工作,封装了CPU和GPU之间的数据交互操作. 补充一点GPU的相关知识: 对CUDA架构而言,主机端的内存 ...
- exchange 2010-备份还原
操作系统:Windows Server 2008R2 \ Exchange2010 测试 1.使用Administraotr用户进行查看己有邮件,如下图: 2.备份Exchange2010整个数据库, ...