NOI 2018 你的名字 (后缀自动机+线段树合并)
题目大意:略
令$ION2017=S,ION2018=T$
对$S$建$SAM$,每次都把$T$放进去跑,求出结尾是i的前缀串,能匹配上$S$的最长后缀长度为$f_{i}$
由于$T$必须在$[l,r]$上匹配,设现在能匹配的长度为$len$,在后缀自动机的$x$点,添加一个字符$c$,则$trs[x][c]$的$right$集合中必须包含$endpos\in[l+len,r]$,这个操作可以用线段树合并实现
否则$len$就要缩短,直到$len$缩短到$dep[pre_{x}]$,$len$如果再缩短就会无意义,跳$pre$,重复上述操作,直到$len$小于0
求出$f_{i}$以后,我们还要对$T$去重,不去重就会爆零
后缀自动机可以看成一个合并后缀的前缀树,相同的后缀是一定会被合并到一起的,每次都跳到能表示$T_{i-f_{i}+1,i}$的位置
由于每次表示的串不一定能取满这个节点,所以答案是$\sum_{2}^{tot} max(0,dep_{x}-max(dep_{pre_{x}},max(f_{k})))$
仔细一想就能明白这个式子的含义了,考虑不合法的情况,要么$x$表示的串全都能被取,要么只能取到最大的经过它的$f_{i}$,取个补集即可
$upd$:这道题卡$AK$的方式也很细节。
我们把$T$放在$S$上匹配,求以每个位置为后缀,在$S$中的最长匹配长度$Len$时
$Len$应该不断$-1$地缩减,直到缩减到$dep[fa]$,再跳到父节点$fa$,而不是直接缩减到$dep[fa]$
因为我们用线段树合并去检验当前答案$len$是否合法,能取的区间是$[l+len,r]$
如果$len$不合法,说明这个节点能表示的,长度为$len$的串不合法。
如果这个节点还能表示其他长度小于$len$的串,就应该依次检查这些串是否都合法。
代码体现就像我上面说的那样,$len$依次$-1$即可。
时间复杂度依然不变,$len$依次$-1$,而我们从左往右遍历整个$T$串,类似于一个双指针,时间复杂度是$O(n)$级别,算上线段树合并就是$O(nlogn)$
#include <cmath>
#include <vector>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N1 500010
#define S1 (N1<<1)
#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-'a')
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,len,cnt;
/*struct Edge{
int head[S1],to[S1],nxt[S1],cte;
void ae(int u,int v){
cte++;to[cte]=v,nxt[cte]=head[u],head[u]=cte;}
}E;*/
struct SEG{
int sum[S1*],ls[S1*],rs[S1*],root[S1],tot;
int merge(int r1,int r2)
{
if(!r1||!r2) return r1+r2;
int nx=++tot;
sum[nx]=sum[r1]+sum[r2];
ls[nx]=merge(ls[r1],ls[r2]);
rs[nx]=merge(rs[r1],rs[r2]);
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]);
}
ll query(int L,int R,int l,int r,int rt)
{
if(!rt||L>R) 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;
}
}s; char str[S1];
int L[S1];//sz[S1],psz[S1];
namespace S{
int trs[S1][],pre[S1],dep[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]+;
for(;p&&!trs[p][c];p=pre[p]) trs[p][c]=np;
s.update(id,,n,s.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];
void topo()
{
int i,x;
for(i=;i<=tot;i++) hs[dep[i]]++;
for(i=;i<=tot;i++) hs[i]+=hs[i-];
for(i=;i<=tot;i++) que[hs[dep[i]]--]=i;
for(i=tot-;i;i--)
{
x=que[i];
s.root[pre[x]]=s.merge(s.root[x],s.root[pre[x]]);
}
}
inline int Min(int X,int Y){return X<Y?X:Y;}
void find(int l,int r)
{
int i,x=,c,mi;
for(i=;i<=len;i++)
{
c=idx(str[i]);
L[i]=L[i-];
while()
{
if(!trs[x][c]||!s.query(l+L[i],r,,n,s.root[trs[x][c]])) {L[i]--;}
else {L[i]++;x=trs[x][c];break;}
if(L[i]==-) {L[i]=;break;}
else if(L[i]==dep[pre[x]]) x=pre[x];
}
/*for(;pre[x]&&(!trs[x][c]||!s.query(l+Min(L[i-1],dep[x]),r,1,n,s.root[trs[x][c]]));x=pre[x]);
if(!trs[x][c]||){
L[i]=0;
}else{
L[i]=min(L[i-1]+1,dep[x]+1);
x=trs[x][c];
}*/
}
}
};
namespace T{
int trs[S1][],pre[S1],dep[S1],la,tot;
void init(){tot=la=;}
void insert(int c)
{
int p=la,np=++tot,q,nq;la=np;
dep[np]=dep[p]+;
for(;p&&!trs[p][c];p=pre[p]) trs[p][c]=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 ma[S1],hs[S1],que[S1];
ll uniq()
{
int i,x,c; ll ans=;
for(i=;i<=tot;i++) hs[dep[i]]++;
for(i=;i<=tot;i++) hs[i]+=hs[i-];
for(i=;i<=tot;i++) que[hs[dep[i]]--]=i;
x=;
for(i=;i<=len;i++)
{
c=idx(str[i]);
for(;pre[x]&&dep[pre[x]]>=L[i]-;x=pre[x]);
x=trs[x][c];
ma[x]=max(ma[x],L[i]);
}
for(i=tot-;i;i--)
{
x=que[i];
if(ma[x]) ma[pre[x]]=dep[pre[x]];
}
for(x=;x<=tot;x++)
ans+=max(,dep[x]-max(dep[pre[x]],ma[x]));
return ans;
}
void clr()
{
tot++;
memset(hs,,tot*),memset(ma,,tot*);
memset(que,,tot*),memset(pre,,tot*);
memset(dep,,tot*),memset(trs,,tot**);
memset(L,,(len+)*);
tot=la=;
}
}; int main()
{
freopen("t2.in","r",stdin);
//freopen("a.out","w",stdout);
int i,j,Q,l,r;ll ans1,ans2;
S::init();
scanf("%s",str+);n=strlen(str+);
for(i=;i<=n;i++) S::insert(idx(str[i]),i);
S::topo();
scanf("%d",&Q);
for(i=;i<=Q;i++)
{
scanf("%s",str+);
scanf("%d%d",&l,&r);
len=strlen(str+);
T::clr();
for(j=;j<=len;j++) T::insert(idx(str[j]));
S::find(l,r);
printf("%lld\n",T::uniq());
}
return ;
}
NOI 2018 你的名字 (后缀自动机+线段树合并)的更多相关文章
- bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并)
bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并) bzoj Luogu 给出一个字符串 $ S $ 及 $ q $ 次询问,每次询问一个字符串 $ T $ ...
- BZOJ5417[Noi2018]你的名字——后缀自动机+线段树合并
题目链接: [Noi2018]你的名字 题目大意:给出一个字符串$S$及$q$次询问,每次询问一个字符串$T$有多少本质不同的子串不是$S[l,r]$的子串($S[l,r]$表示$S$串的第$l$个字 ...
- BZOJ.5417.[NOI2018]你的名字(后缀自动机 线段树合并)
LOJ 洛谷 BZOJ 考虑\(l=1,r=|S|\)的情况: 对\(S\)串建SAM,\(T\)在上面匹配,可以得到每个位置\(i\)的后缀的最长匹配长度\(mx[i]\). 因为要去重,对\(T\ ...
- luogu4770 [NOI2018]你的名字 后缀自动机 + 线段树合并
其实很水的一道题吧.... 题意是:每次给定一个串\(T\)以及\(l, r\),询问有多少个字符串\(s\)满足,\(s\)是\(T\)的子串,但不是\(S[l .. r]\)的子串 统计\(T\) ...
- BZOJ3413: 匹配(后缀自动机 线段树合并)
题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并... 首先可以转化一下模型(想不到qwq):问题可以转化为统计\(B\)中每个前缀在\(A\)中出现的次数.(画一画就出来了) 然后直 ...
- 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]\)的最长公共前缀. 二分 首先我们可以发现一个简单性质,即要求最长公共前缀,则我 ...
随机推荐
- 会话cookie和持久化cookie实现session
当你第一次访问一个网站的时候,网站服务器会在响应头内加上Set- Cookie:PHPSESSID=nj1tvkclp3jh83olcn3191sjq3(php服务器),或Set-Cookie JSE ...
- Swoole 源码分析——基础模块之 Pipe 管道
前言 管道是进程间通信 IPC 的最基础的方式,管道有两种类型:命名管道和匿名管道,匿名管道专门用于具有血缘关系的进程之间,完成数据传递,命名管道可以用于任何两个进程之间.swoole 中的管道都是匿 ...
- omap 移植qt4.7.0
准备: 1.Qt源码包 qt-everywhere-opensource-src-4.7.0.tar.gz 2.交叉编译器 arm-eabi-4.4.0.tar.bz2 3.触摸屏校验工具:tslib ...
- 自学python到找到工作的心得
先做个自我介绍,我13年考上一所很烂专科民办的学校,学的是生物专业,具体的学校名称我就不说出来献丑了.13年我就辍学了,我在那样的学校,一年学费要1万多,但是根本没有人学习,我实在看不到希望,我就退学 ...
- 统制Highcharts中x轴和y轴坐标值的密度
统制Highcharts中x轴和y轴坐标值的密度 www.MyException.Cn 发布于:2012-06-26 10:04:13 浏览:688次 1 控制Highcharts中x轴和y轴坐标值的 ...
- 【百度语音识别】JavaAPI方式语音识别示例
https://ai.baidu.com/forum/topic/show/496730
- [SharePoint2010开发入门经典]SPS2010开发工具
本章概要: 1.了解不同的开发SPS的方法 2.了解SPS开发工具和环境 3.使用VS2010和SPD还有Blend开发SPS
- System v shm的key
shmget函数用于创建或打开一个共享内存区对象,shmget成功调用会返回一个共享内存区的标识符,供其它的共享内存区操作函数使用. key:用于创建共享内存区的键值,这个在前面其他System IP ...
- 动态为TextView控件设置drawableLeft图标,并设置间距
效果图: 重要属性: textView.setCompoundDrawablePadding(4);//设置图片和text之间的间距 textView.setPadding(-5, 0, 0, 0); ...
- jsp 传值jsp 数据库 乱码解决的攻略 全套
jsp传值给jsp中文乱码 传值给数据库乱码的解决方法 所有的用到编码的所有统一utf-8 1.装mysql的时候有选择编码的界面的那个地方选utf-8编码 2 建数据库的时候选择 字符集 排序规则所 ...