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. Windows下Anaconda的安装和简单使用

    Windows下Anaconda的安装和简单使用 Anaconda is a completely free Python distribution (including for commercial ...

  2. openstack 安全策略权限控制等api接口

    computer API:                创建安全组 /os-security-groups 创建安全组规则 /os-security-group-default-rules Netw ...

  3. (常用)re模块

    re模块(正则)#re:一些带有特殊含义的符号或者符号的组合#为什么要用re:一堆字符串中找到你所需要的内容,过滤规则是什么样,通过re模块功能来告诉计算机你的过滤规则#应用:在爬虫中最为常用:使用爬 ...

  4. Web 中调用FreeSWITCH的Portal GUI配置记录

    具体设定步骤: ①加载 mod_xml_rpc 模块:load mod_xml_rpc 若想让该模块在FreeSWITCH启动时而自动加载,在conf/autoload_configs/modules ...

  5. discuz3.4:在Centos6.5中安装过程

    参考文章:https://www.cnblogs.com/hehongbin/articles/5741270.html https://www.cnblogs.com/mitang/p/552454 ...

  6. 解决Jmeter插件ERROR: java.io.IOException: Agent is unreachable via TCP的错误

    今天在centos上搭建jmeter监控服务,服务正常启动,我点击run,就在一切看起来很美好的时候,报错了,ERROR: java.io.IOException: Agent is unreacha ...

  7. VirtualBox虚拟机安装Mac OS 10.12

    注:本文作者  (QQ:85805214) 本博主只是转载发布而已. VMware 安装Mac OS 方法 由于Virtual Box拷贝文件比较麻烦,有时候经常出现拷贝失败的情况,故使用VMware ...

  8. InterruptedException 异常

    当一个方法后面声明可能会抛出InterruptedException 异常时,说明该方法是可能会花一点时间,但是可以取消的方法. 抛InterruptedException的代表方法有: 1. jav ...

  9. python接口自动化测试二十:函数写接口测试

    # coding:utf-8import requestsimport refrom bs4 import BeautifulSoup # s = requests.session() # 全局的s ...

  10. 统计uv(转)

    UV是unique visitor的简写,是指通过互联网访问.浏览这个网页的自然人.在同一天内,uv只记录第一次进入网站的具有独立IP的访问者,在同一天内再次访问该网站则不计数.独立IP访问者提供了一 ...