BZOJ 3413 匹配 (后缀自动机+线段树合并)
题目大意:
.jpg)
懒得概括了
神题,搞了2个半晚上,还认为自己的是对的...一直调不过,最后终于在jdr神犇的帮助下过了这道题
线段树合并该是这道题最好理解且最好写的做法了,貌似主席树也行?但线段树合并这个算法实在是太优美了
一个模式串从左到右为开头进行匹配,如果在前面已经匹配成功了,后面就算能匹配成功也没用
因此在$parent$树里维护一个数组$mi_{x}$,表示在$parent$树中,节点$x$的子树中$len_{x}$的最小值,可以用桶+拓扑$O(n)$实现
如果一个模式串$T$是$S$的一个子串
首先用上面维护的$mi_{x}$数组找出这个串能被匹配上的,第一个末尾位置$pos$
显然,以$[1,pos-len]$为开头,向后进行暴力匹配,都匹配不出$T$,每个位置为开头都失配一次,失配的总长度是$pos-len$
接下来就是解决以$[1,pos-len]$为开头,能匹配上$T$的一小部分前缀的情况了
直接讨论每个位置最多能往后匹配多长,会很复杂(如果大家想看这种做法可以看大师的博客)
转化问题
我们讨论$T$的每个前缀,在$S$一定范围内的前缀中,作为后缀出现几次不就行了
我们把$T$串放到$trs$图里跑
现在走到了一个节点$x$,已经走过的路径长度是$i$,它的$right$集合可以用线段树合并预处理出来,我们只需要求出$x$的$parent$子树内,$len$小于某个上限的$endpos$节点数量就行了
推导可得,这个上限是$pos-len+i$,因为再往后就会超出第一次匹配的位置,不可行
如果$T$不是$S$的一个子串,失配长度就是$n$,上限也全都改成$n$就行了
而且$endpos$节点的$len$互不相同,恰好契合了线段树合并的性质,预处理的时候从叶节点一直往上合并即可
#include <cmath>
#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N1 105000
#define S1 (N1<<1)
#define T1 (N1<<2)
#define M1 105000
#define ll long long
#define uint unsigned int
#define rint register int
#define dd double
#define il inline
#define inf 0x3f3f3f3f
#define idx(X) (X-'0')
using namespace std; int gint()
{
int ret=,fh=;char c=getchar();
while(c<''||c>''){if(c=='-')fh=-;c=getchar();}
while(c>=''&&c<=''){ret=ret*+c-'';c=getchar();}
return ret*fh;
}
int n,m,len,de;
char str[N1];
struct Edge{
int to[S1],nxt[S1],head[S1],cte;
void ae(int u,int v){
cte++;to[cte]=v,nxt[cte]=head[u],head[u]=cte;}
}E;
namespace seg{
int ls[S1*],rs[S1*],root[S1],tot;
ll sum[S1*];
int merge(int rt1,int rt2)
{
if(!rt1||!rt2) return rt1+rt2;
int nx=++tot;
sum[nx]=sum[rt1]+sum[rt2];
ls[nx]=merge(ls[rt1],ls[rt2]);
rs[nx]=merge(rs[rt1],rs[rt2]);
return nx;
}
void update(int x,int l,int r,int &rt)
{
if(!rt) rt=++tot;
sum[rt]=;
if(l==r) return;
int mid=(l+r)>>;
if(x<=mid) update(x,l,mid,ls[rt]);
else update(x,mid+,r,rs[rt]);
//pushup(rt);
}
ll query(int L,int R,int l,int r,int rt)
{
if(!rt) return ;
if(L<=l&&r<=R) return sum[rt];
int mid=(l+r)>>;ll ans=;
if(L<=mid) ans+=query(L,R,l,mid,ls[rt]);
if(R>mid) ans+=query(L,R,mid+,r,rs[rt]);
return ans;
}
};
namespace SAM{
int trs[S1][],pre[S1],dep[S1],ed[S1],mi[S1],la,tot;
void init(){tot=la=;}
void insert(int c,int id)
{
int p=la,np=++tot,q,nq;la=np;
dep[np]=dep[p]+;ed[np]=id;
for(;p&&!trs[p][c];p=pre[p]) trs[p][c]=np;
seg::update(id,,n,seg::root[np]);
if(!p){pre[np]=;return;}
q=trs[p][c];
if(dep[q]==dep[p]+) pre[np]=q;
else{
pre[nq=++tot]=pre[q];
pre[q]=pre[np]=nq;
dep[nq]=dep[p]+;
memcpy(trs[nq],trs[q],sizeof(trs[q]));
for(;p&&trs[p][c]==q;p=pre[p]) trs[p][c]=nq;
}
}
int hs[S1],que[S1],edq[S1],k,l,tt;
void build()
{
//memset(mi,0x3f,sizeof(mi));
for(int i=;i<=tot;i++) mi[i]=n+;
for(int i=;i<=tot;i++) hs[dep[i]]++;
for(int i=;i<=n;i++) hs[i]+=hs[i-];
for(int i=;i<=tot;i++) que[hs[dep[i]]--]=i;
for(int i=tot-,x;i>=;i--)
{
x=que[i],E.ae(pre[x],x);
if(ed[x]) mi[x]=min(mi[x],ed[x]);
mi[pre[x]]=min(mi[pre[x]],mi[x]);
seg::root[pre[x]]=seg::merge(seg::root[pre[x]],seg::root[x]);
}
}
void find(int L,int &F)
{
int x=,c,fl=;
for(int i=;i<=L;i++)
{
c=idx(str[i]);
//for(;x&&!trs[x][c];x=pre[x]);
if(!trs[x][c]) return;
x=trs[x][c];
}
F=mi[x];
}
}; int main()
{
scanf("%d",&n);
scanf("%s",str+);
SAM::init();
for(int i=;i<=n;i++)
SAM::insert(idx(str[i]),i);
SAM::build();
scanf("%d",&m);
int F,c,x;
for(int i=;i<=m;i++)
{
scanf("%s",str+);
len=strlen(str+);
F=n+;
SAM::find(len,F);
ll ans=;
if(F!=n+) ans=F-len;
else ans=n;
x=;
for(int j=;j<=len;j++)
{
c=idx(str[j]);
//for(;x&&!SAM::trs[x][c];x=SAM::pre[x]);
x=SAM::trs[x][c];
if(!x) break;
ans+=seg::query(,(F==n+?n:F-len+j),,n,seg::root[x]);
}
printf("%lld\n",ans);
}
return ;
}
BZOJ 3413 匹配 (后缀自动机+线段树合并)的更多相关文章
- BZOJ3413: 匹配(后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并... 首先可以转化一下模型(想不到qwq):问题可以转化为统计\(B\)中每个前缀在\(A\)中出现的次数.(画一画就出来了) 然后直 ...
- bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并)
bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并) bzoj Luogu 给出一个字符串 $ S $ 及 $ q $ 次询问,每次询问一个字符串 $ T $ ...
- cf666E. Forensic Examination(广义后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...
- [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]\)的最长公共前缀. 二分 首先我们可以发现一个简单性质,即要求最长公共前缀,则我 ...
- BZOJ.5417.[NOI2018]你的名字(后缀自动机 线段树合并)
LOJ 洛谷 BZOJ 考虑\(l=1,r=|S|\)的情况: 对\(S\)串建SAM,\(T\)在上面匹配,可以得到每个位置\(i\)的后缀的最长匹配长度\(mx[i]\). 因为要去重,对\(T\ ...
- BZOJ5417[Noi2018]你的名字——后缀自动机+线段树合并
题目链接: [Noi2018]你的名字 题目大意:给出一个字符串$S$及$q$次询问,每次询问一个字符串$T$有多少本质不同的子串不是$S[l,r]$的子串($S[l,r]$表示$S$串的第$l$个字 ...
- CF 666E Forensic Examination——广义后缀自动机+线段树合并
题目:http://codeforces.com/contest/666/problem/E 对模式串建广义后缀自动机,询问的时候把询问子串对应到广义后缀自动机的节点上,就处理了“区间”询问. 还要处 ...
随机推荐
- sessionStorage缓存滚动条位置
想象在一个列表页,用户上滑页面浏览数据,点击某一条进入详情页,之后再从详情页返回列表页时不会想再从头去查看数据,这就要求我们记录用户刚刚浏览的位置,而不是重新刷新页面到了页面顶部.这里需要用到sess ...
- uni-app 路由navigate
uni-app 是一个使用 Vue.js 开发跨平台应用的前端框架,开发者编写一套代码,可编译到iOS.Android.H5.小程序等多个平台. 公司最近在写APP应用到了uni-app 我在写的时 ...
- Java的五大原则
五个基本原则: 单一职责原则(Single-Resposibility Principle):一个类,最好只做一件事,只有一个引起它的变化.单一职责原则可以看做是低耦合.高内聚在面向对象原则上的引申, ...
- 页面与后台传递中文乱码问题(java乱码)
1.前台中文传递到后台乱码. 前台不须要处理, 系统一般都会默认把中文转化为ISO-8859-1类型. 仅仅需在后台接受数据是处理 Str为前台传过来的中文字符串: String inputer = ...
- 回想四叉树LOD地形(上)
唉.~事实上这是在差点儿相同一年前实现的东西,但当时没作好记录.放了那么久了,假设不做点总结的话,好像有点对不起自己,于是·········还是做点什么吧. 我脑洞比較小, ...
- XML系统学习
参考:W3School XML基本概念 1.XML是eXtensible Markup Language,使用DTD(Document Type Definition)来描述数据,主要是为传输和存储数 ...
- BZOJ 2728 HNOI2012 与非 高斯消元
题目大意:给定k位二进制下的n个数,求[l,r]区间内有多少个数能通过这几个数与非得到 首先观察真值表 我们有A nand A = not A 然后就有not ( A nand B ) = A and ...
- 【DataStructure】The difference among methods addAll(),retainAll() and removeAll()
In the Java collection framework, there are three similar methods, addAll(),retainAll() and removeAl ...
- tensorflow利用预训练模型进行目标检测(二):预训练模型的使用
一.运行样例 官网链接:https://github.com/tensorflow/models/blob/master/research/object_detection/object_detect ...
- EMC存储划分lun过程
下图是EMC存储系统示意图: 若将lun打散重建,需按以下步骤进行: 1. 在Storage Groups上点右键选择Select Luns,在打开的窗口中,将右边Selected Lun项下的lun ...