LOJ

洛谷

BZOJ

考虑\(l=1,r=|S|\)的情况:

对\(S\)串建SAM,\(T\)在上面匹配,可以得到每个位置\(i\)的后缀的最长匹配长度\(mx[i]\)。

因为要去重,对\(T\)也建SAM,计算上面所有节点的答案。记\(pos[i]\)表示\(i\)节点第一次出现的下标(同一节点代表的串出现的位置集合相同,所以随便记一个即可)。

则节点\(i\)的答案为:\(\max(0,\ len[i]-\max(len[fa[i]],\ mx[pos[i]]))\)。

考虑\(l,r\)任意的情况:

要判断\(T\)能否在\(S[l,r]\)上匹配,也就是匹配的时候只能走在\(S[l,r]\)出现过的节点。

线段树合并,自底向上合并right集合,就可以得到SAM上每个节点出现过的位置(right)。

如果已匹配长度为\(now\),那么区间查的时候要查\([l+now,r]\)啊(跳\(fa\)时要同时改\(now\))(要不还是68分)。

而且\(now\)应该是每次减一,而不是令\(p\)直接跳\(fa\)(但还是有96分)。

好像只要还记得SAM的套路就没那么难?(反正我已经忘了)

明年还出SAM就好了。

/*
LOJ:10770ms 337788K
洛谷:16362ms 348.41MB
BZOJ:408052kb 28352ms
*/
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define gc() getchar()
typedef long long LL;
const int N=1e6+5; int mx[N],root[N];
char s[N];
struct Segment_Tree
{
#define ls son[x][0]
#define rs son[x][1]
#define lson ls,l,m
#define rson rs,m+1,r
#define S N*20
int tot,son[S][2];
#undef S
void Insert(int &x,int l,int r,int p)
{
/*if(!x)*/ x=++tot;//
if(l==r) return;
int m=l+r>>1;
p<=m ? Insert(lson,p) : Insert(rson,p);
}
int Merge(int x,int y)
{
if(!x||!y) return x|y;
int now=++tot;
son[now][0]=Merge(ls,son[y][0]), son[now][1]=Merge(rs,son[y][1]);
return now;
}
bool Query(int x,int l,int r,int L,int R)
{
if(!x) return 0;
if(L<=l && r<=R) return 1;
int m=l+r>>1;
if(L<=m)
if(m<R) return Query(lson,L,R)||Query(rson,L,R);
else return Query(lson,L,R);
return Query(rson,L,R);
}
#undef ls
#undef rs
}Tr;
struct Suffix_Automaton
{
int n,tot,las,fa[N],son[N][26],len[N],tm[N],A[N],pos[N]; void Insert(int c,int id)
{
int np=++tot,p=las;
len[las=np]=len[p]+1, pos[np]=id;
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, pos[nq]=pos[q];
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;
}
}
}
void Build_S(char *s,int l)
{
memset(son,0,(tot+2)*sizeof son[0]);
tot=las=1, n=l;
for(int i=1; i<=n; ++i) Insert(s[i]-'a',i),Tr.Insert(root[las],1,n,i);
for(int i=1; i<=tot; ++i) ++tm[len[i]];
for(int i=1; i<=n; ++i) tm[i]+=tm[i-1];
for(int i=1; i<=tot; ++i) A[tm[len[i]]--]=i;
for(int i=tot,x=A[i]; i>1; x=A[--i]) root[fa[x]]=Tr.Merge(root[x],root[fa[x]]);
}
void Build_T(char *s,int l)
{
memset(son,0,(tot+2)*sizeof son[0]);
tot=las=1, n=l;
for(int i=1; i<=l; ++i) Insert(s[i]-'a',i);
}
}S,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 Solve(int ls)
{
scanf("%s",s+1);
int l=read(),r=read(),n=strlen(s+1);
T.Build_T(s,n);
for(int i=1,now=0,p=1,c; i<=n; mx[i++]=now)
{
c=s[i]-'a';
while(1)
{
if(!Tr.Query(root[S.son[p][c]],1,ls,l+now,r))
{
if(!now) break;//匹配长度为0了要再匹配一次
--now;
if(now==S.len[S.fa[p]]) p=S.fa[p];
}
else {++now, p=S.son[p][c]; break;}
}
// if(Tr.Query(root[S.son[p][c]],1,ls,l+now,r)) p=S.son[p][c], ++now;
// else
// {
// for(; p&&!Tr.Query(root[S.son[p][c]],1,ls,l+now,r); p=S.fa[p],now=S.len[p]);
// if(!p) p=1, now=0;
// else now=S.len[p]+1, p=S.son[p][c];//这样写96分 还只错了一个询问
// }
}
LL ans=0;
for(int i=2; i<=T.tot; ++i)
ans+=std::max(0,T.len[i]-std::max(T.len[T.fa[i]],mx[T.pos[i]]));
printf("%lld\n",ans);
} int main()
{
// freopen("name.in","r",stdin);
// freopen("name.out","w",stdout); scanf("%s",s+1); int ls=strlen(s+1);
S.Build_S(s,ls);
for(int Q=read(); Q--; Solve(ls)); return 0;
}

68分代码:

/*
对S串建SAM,T在上面匹配,可以得到每个位置i的后缀的最长匹配长度mx[i]。
因为要去重,对T也建SAM,计算上面所有节点的答案。记pos[i]表示i节点第一次出现的下标(同一节点代表的串出现的位置集合相同)。
则节点i的答案为:max(0, len[i]-max(len[fa[i]], mx[pos[i]]))。
*/
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define gc() getchar()
typedef long long LL;
const int N=1e6+5; int mx[N];
char s[N];
struct Suffix_Automaton
{
int n,tot,las,fa[N],son[N][26],len[N],pos[N]; void Insert(int c,int id)
{
int np=++tot,p=las;
len[las=np]=len[p]+1, pos[np]=id;
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, pos[nq]=pos[q];
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;
}
}
}
void Build(char *s,int l)
{
memset(son,0,(tot+2)*sizeof son[0]);
tot=las=1, n=l;
for(int i=1; i<=n; ++i) Insert(s[i]-'a',i);
}
}S,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 Solve()
{
int n=strlen(s+1);
for(int i=1,now=0,p=1,c; i<=n; mx[i++]=now)
{
if(S.son[p][c=s[i]-'a']) p=S.son[p][c], ++now;
else
{
for(; p&&!S.son[p][c]; p=S.fa[p]);
if(!p) p=1, now=0;
else now=S.len[p]+1, p=S.son[p][c];
}
}
LL ans=0;
for(int i=2; i<=T.tot; ++i)
ans+=std::max(0,T.len[i]-std::max(T.len[T.fa[i]],mx[T.pos[i]]));
printf("%lld\n",ans);
} int main()
{
freopen("name.in","r",stdin);
freopen("name.out","w",stdout); scanf("%s",s+1);
S.Build(s,strlen(s+1)); int Q=read();
for(int i=1; i<=Q; ++i)
{
scanf("%s",s+1);
int l=read(),r=read();
T.Build(s,strlen(s+1)), Solve();
}
return 0;
}

BZOJ.5417.[NOI2018]你的名字(后缀自动机 线段树合并)的更多相关文章

  1. bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并)

    bzoj5417/luoguP4770 [NOI2018]你的名字(后缀自动机+线段树合并) bzoj Luogu 给出一个字符串 $ S $ 及 $ q $ 次询问,每次询问一个字符串 $ T $ ...

  2. BZOJ5417[Noi2018]你的名字——后缀自动机+线段树合并

    题目链接: [Noi2018]你的名字 题目大意:给出一个字符串$S$及$q$次询问,每次询问一个字符串$T$有多少本质不同的子串不是$S[l,r]$的子串($S[l,r]$表示$S$串的第$l$个字 ...

  3. luogu4770 [NOI2018]你的名字 后缀自动机 + 线段树合并

    其实很水的一道题吧.... 题意是:每次给定一个串\(T\)以及\(l, r\),询问有多少个字符串\(s\)满足,\(s\)是\(T\)的子串,但不是\(S[l .. r]\)的子串 统计\(T\) ...

  4. NOI 2018 你的名字 (后缀自动机+线段树合并)

    题目大意:略 令$ION2017=S,ION2018=T$ 对$S$建$SAM$,每次都把$T$放进去跑,求出结尾是i的前缀串,能匹配上$S$的最长后缀长度为$f_{i}$ 由于$T$必须在$[l,r ...

  5. [NOI2018]你的名字(后缀自动机+线段树)

    题目描述 小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了. 由于ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手 ...

  6. BZOJ3413: 匹配(后缀自动机 线段树合并)

    题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并... 首先可以转化一下模型(想不到qwq):问题可以转化为统计\(B\)中每个前缀在\(A\)中出现的次数.(画一画就出来了) 然后直 ...

  7. cf666E. Forensic Examination(广义后缀自动机 线段树合并)

    题意 题目链接 Sol 神仙题Orz 后缀自动机 + 线段树合并 首先对所有的\(t_i\)建个广义后缀自动机,这样可以得到所有子串信息. 考虑把询问离线,然后把\(S\)拿到自动机上跑,同时维护一下 ...

  8. [Luogu5161]WD与数列(后缀数组/后缀自动机+线段树合并)

    https://blog.csdn.net/WAautomaton/article/details/85057257 解法一:后缀数组 显然将原数组差分后答案就是所有不相交不相邻重复子串个数+n*(n ...

  9. 模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合)

    模板—字符串—后缀自动机(后缀自动机+线段树合并求right集合) Code: #include <bits/stdc++.h> using namespace std; #define ...

随机推荐

  1. 泰克TDS1000B示波器使用说明

    1.前言 本文主要根据泰克官方网站TDS1000B/TDS2000B使用教程视频进行整理. 2.认识你的示波器 TDS1000B带宽从40MHZ到200MHZ,采样率高达2Gbps

  2. java中集合的组成及特点

    1:集合 Collection(单列集合) List(有序,可重复) ArrayList 底层数据结构是数组,查询快,增删慢 线程不安全,效率高 Vector 底层数据结构是数组,查询快,增删慢 线程 ...

  3. 通过htaccess文件配置多个一级域名指向根目录的子文件夹

    创建.htaccess文件,在Windows系统创建时要写成“.htaccess.”,不带双引号,否则不会创建成功. <IfModule mod_rewrite.c> Options +F ...

  4. saltstack自动化运维系列11基于etcd的saltstack的自动化扩容

    saltstack自动化运维系列11基于etcd的saltstack的自动化扩容 自动化运维-基于etcd加saltstack的自动化扩容# tar -xf etcd-v2.2.1-linux-amd ...

  5. cocos creator 的scorllview 滑动事件和 子内容触摸事件会产生冲突

    1:问题描叙: UI上的 scorllview 的子元素需要拖动到游戏场景.所以子元素需要绑定触摸事件,scorllview 默认的事件处理方式就会和子元素的触摸事件冲突.2:解决方案: Scroll ...

  6. vue-router两种模式,到底什么情况下用hash,什么情况下用history模式呢?

    转:https://segmentfault.com/q/1010000010340823/a-1020000010598395 为什么要有 hash 和 history 对于 Vue 这类渐进式前端 ...

  7. vue+element之多表单验证

    方法一:利用promise var p1=new Promise(function(resolve, reject) { this.$refs[form1].validate((valid) => ...

  8. RzPageControl Tab拖拽 移动

  9. hdu3193 降维+rmq

    /* 给定n个数对(p,d),如果有这么一个数对(pi,di),其他所有的数对都不能严格小于这个数对 请求出有多少个这样的数对! 解法:对于数对(p,d),能找到在[1,p-1]之间的小于d的数对,那 ...

  10. (三)使用CXF开发WebService客户端

    前面一讲开发了webservice服务器端接口,今天的话,我们来开发webservice客户端,让大家来体验下过程: 首先建一个Maven项目,项目名字,WS_Client: 然后我们要用CXF给我们 ...