BZOJ.5417.[NOI2018]你的名字(后缀自动机 线段树合并)
考虑\(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]你的名字(后缀自动机 线段树合并)的更多相关文章
- 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$个字 ...
- luogu4770 [NOI2018]你的名字 后缀自动机 + 线段树合并
其实很水的一道题吧.... 题意是:每次给定一个串\(T\)以及\(l, r\),询问有多少个字符串\(s\)满足,\(s\)是\(T\)的子串,但不是\(S[l .. r]\)的子串 统计\(T\) ...
- NOI 2018 你的名字 (后缀自动机+线段树合并)
题目大意:略 令$ION2017=S,ION2018=T$ 对$S$建$SAM$,每次都把$T$放进去跑,求出结尾是i的前缀串,能匹配上$S$的最长后缀长度为$f_{i}$ 由于$T$必须在$[l,r ...
- [NOI2018]你的名字(后缀自动机+线段树)
题目描述 小A 被选为了ION2018 的出题人,他精心准备了一道质量十分高的题目,且已经把除了题目命名以外的工作都做好了. 由于ION 已经举办了很多届,所以在题目命名上也是有规定的,ION 命题手 ...
- 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 ...
随机推荐
- 【Python】zip文件密码破解
掌握基础语法后,尝试使用python的zipfile模块练手. zipfile是Python里用来做zip格式编码的压缩和解压缩的. 这里将大体的思路分解成四段代码,逐一完善功能: 第一段代码:解压z ...
- MySQL— 索引
目录 一.索引 二.索引类型 三.索引种类 四.操作索引 五.创建索引的时机 六.命中索引 七.其它注意事项 八.LIMIT分页 九.执行计划 十.慢查询日志 一.索引 MySQL索引的建立对于MyS ...
- UML和模式应用4:初始阶段(1)--概述
1.前言 UP开发包括四个阶段:初始阶段.细化阶段.构建阶段.移交阶段: 初始阶段是项目比较简短的起始步骤,主要目的是收集足够的信息来建立共同设想,调查项目的总体目标和可行性,确定是否值得进一步深入. ...
- NoNodeAvailableException[None of the configured nodes are available: [{#transport#-1}{HBmUtjMOQP2pgLFFwqa_Og}{172.16.0.163}{172.16.0.163:9300}] ]
1.找到elasticsearch的安装目录,在config目录找到elasticsearch.yml,查看cluster.name的赋值 2.在SpringBoot的yml文件中,不仅要配置clus ...
- 转载:编译安装Nginx(1.5.1)《深入理解Nginx》(陶辉)
原文:https://book.2cto.com/201304/19618.html 1.5 configure详解 可以看出,configure命令至关重要,下文将详细介绍如何使用configure ...
- 常用的Unicode值范围
汉字:[0x4e00,0x9fa5](或十进制[19968,40869])数字:[0x30,0x39](或十进制[48, 57])小写字母:[0x61,0x7a](或十进制[97, 122])大写字母 ...
- 常见的js算法
参考地址:链接
- java多线程快速入门(一)
1.什么是进程 比如:QQ.QQ游戏.eclipse都是进程,可以通过任务管理器查看进程 2.进程和线程区别 线程是进程的一部分,一个进程可以包含多个线程,一个线程只能属于一个进程 进程是所有线程的集 ...
- 线段树解LIS
先是nlogn的LIS解法 /* LIS nlogn解法 */ #include<iostream> #include<cstring> #include<cstdio& ...
- uva11983扫描线k次覆盖
自己做的是从下往上扫描的,一直wa,不知道坑在哪里..但是作为模板.我还是找了份不错的ac代码 /* 被覆盖不低于k次的点 每个点对应了一个单位面积,本题把点转面积即是被覆盖不低于k次的面积 可以当做 ...