传送门

我有种自己根本没学过SAM的感觉……最后还是抄了老半天的题解……

首先,对$S$和每一次的$T$都建一个SAM

先考虑一下$l=1,r=\left| S \right|$的情况

设$lim_i$表示字符串$T[1..i]$能在$S$中匹配到的最长后缀(即$T[i-lim_i+1,i]$是$S$的子串且$lim_i$最大)(有可能不存在这个字符那么$lim_i=0$)

这个$lim_i$可以不断地在$S$的后缀自动机上跳来求出。当无法向下匹配时,一直跳parent树直到可以匹配为止

我们假设对于$T$的后缀自动机,每一个节点的$endpos$集合中所能代表的最长的字符串长度为$l_i$,$tag_i$表示该集合字符串第一次出现的结尾位置(因为集合里字符串互为后缀所以结尾相同),$fa_i$表示parent树上的父亲,$cnt$表示自动机节点总个数

那么答案就是$$ans=\sum_{i=2}^{cnt}max(0,l_i-max(l_{fa_i},lim_{tag_i}))$$

ps:这里的lim指的并不是上文的lim而是最长后缀的长度

上面式子的意思是,对于每一个节点,它不属于$S$的子串的总个数为当前节点代表的集合字符串个数减去与$S$有匹配的子串个数

然后只要在$T$的后缀自动机上枚举每一个节点就可以了

现在来考虑$l$和$r$任意的情况该怎么做

这个时候就要用线段树维护后缀自动机的$endpos$集合了(不明白这个怎么做的我简单说一下,就是搞一个动态开点线段树,如果一个节点的$endpos$集合里有某一个位置就把它加入以该点为根的树中,parent树上父亲节点的$endpos$集合必然包含儿子的$endpos$集合所以将每个点的$endpos$集合与它儿子的合并。然后查询这个节点是否有$endpos$位于某个区间中只要在线段树上查询看看这个区间代表的节点是否被开出来过就好了(因为线段树上只有存在的位置的节点被开出来过))

我们在处理$lim_i$集合的时候要注意,要判断当前节点是否在$S[l..r]$区间中出现过。设$p$为当前在$S$的自动机上跑到的节点,$len$表示匹配了$[i-len+1..i]$,因为要看能否转移到下一个节点,所以要匹配$[i-len,i]$,且$[l+len,r]$中有后继节点的$endpos$存在才行(可能说的烦了点,仔细想想为什么)

差不多就这样

 //minamoto
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
char sr[<<],z[];int C=-,Z;
inline void Ot(){fwrite(sr,,C+,stdout),C=-;}
inline void print(ll x){
if(C><<)Ot();
while(z[++Z]=x%+,x/=);
while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
const int N=1e6+,M=2e7+,inf=0x3f3f3f3f;
int q,n,m,rt[N],lim[N];char S[N];ll ans;
namespace tree{
int cnt,L[M],R[M];
void ins(int &p,int l,int r,int x){
if(!p) p=++cnt;
if(l==r) return;
int mid=(l+r)>>;
if(x<=mid) ins(L[p],l,mid,x);
else ins(R[p],mid+,r,x);
}
int merge(int x,int y){
if(!x||!y) return x+y;
int p=++cnt;
L[p]=merge(L[x],L[y]);
R[p]=merge(R[x],R[y]);
return p;
}
bool query(int p,int l,int r,int ql,int qr){
if(!p||ql>qr) return false;
if(ql<=l&&qr>=r) return true;
int mid=(l+r)>>;
if(ql<=mid&&query(L[p],l,mid,ql,qr)) return true;
if(qr>mid&&query(R[p],mid+,r,ql,qr)) return true;
return false;
}
}
namespace SAM{
int cnt=,last=;
int ch[N][],l[N],fa[N],c[N],a[N],in[N];
void ins(int c){
int p=last,np=++cnt;last=np,l[np]=l[p]+,in[np]=;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
if(!p) fa[np]=;
else{
int q=ch[p][c];
if(l[q]==l[p]+) fa[np]=q;
else{
int nq=++cnt;l[nq]=l[p]+;
memcpy(ch[nq],ch[q],sizeof(int)*());
fa[nq]=fa[q],fa[q]=fa[np]=nq;
for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
inline void calc(){
for(int i=;i<=n;++i) ins(S[i]-'a');
for(int i=;i<=cnt;++i) ++c[l[i]];
for(int i=;i<=cnt;++i) c[i]+=c[i-];
for(int i=;i<=cnt;++i) a[c[l[i]]--]=i;
for(int i=cnt,p;i;--i){
p=a[i];
if(in[p]) tree::ins(rt[p],,n,l[p]);
rt[fa[p]]=tree::merge(rt[fa[p]],rt[p]);
}
}
}
namespace solve{
int cnt=,last=;
int ch[N][],l[N],fa[N],c[N],a[N],tag[N];
inline void init(){
cnt=last=,memset(ch[],,sizeof(int)*());
}
inline int newnode(){
++cnt;memset(ch[cnt],,sizeof(int)*());return cnt;
}
void ins(int c){
int p=last,np=newnode();last=np,tag[np]=l[np]=l[p]+;
for(;p&&!ch[p][c];p=fa[p]) ch[p][c]=np;
if(!p) fa[np]=;
else{
int q=ch[p][c];
if(l[q]==l[p]+) fa[np]=q;
else{
int nq=newnode();l[nq]=l[p]+,tag[nq]=tag[q];
memcpy(ch[nq],ch[q],sizeof(int)*());
fa[nq]=fa[q],fa[q]=fa[np]=nq;
for(;ch[p][c]==q;p=fa[p]) ch[p][c]=nq;
}
}
}
ll solve(){
int L,R;
scanf("%s%d%d",S+,&L,&R);
init();
m=strlen(S+);
for(int len=,p=,i=;i<=m;++i){
int c=S[i]-'a';
ins(c);
while(true){
if(SAM::ch[p][c]&&tree::query(rt[SAM::ch[p][c]],,n,L+len,R)){
++len,p=SAM::ch[p][c];break;
}
if(len==) break;
--len;
if(len==SAM::l[SAM::fa[p]]) p=SAM::fa[p];
}
lim[i]=len;
}
ans=;
for(int i=;i<=cnt;++i)
ans+=max(,l[i]-max(l[fa[i]],lim[tag[i]]));
return ans;
}
}
int main(){
// freopen("testdata.in","r",stdin);
scanf("%s",S+);
n=strlen(S+);
SAM::calc();
scanf("%d",&q);
while(q--) print(solve::solve());
Ot();
return ;
}

洛谷P4770 [NOI2018]你的名字(后缀自动机+线段树)的更多相关文章

  1. 洛谷P4770 [NOI2018]你的名字 [后缀自动机,线段树合并]

    传送门 思路 按照套路,直接上后缀自动机. 部分分:\(l=1,r=|S|\) 首先把\(S\)和\(T\)的后缀自动机都建出来. 考虑枚举\(T\)中的右端点\(r\),查询以\(r\)结尾的串最长 ...

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

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

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

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

  4. BZOJ.5417.[NOI2018]你的名字(后缀自动机 线段树合并)

    LOJ 洛谷 BZOJ 考虑\(l=1,r=|S|\)的情况: 对\(S\)串建SAM,\(T\)在上面匹配,可以得到每个位置\(i\)的后缀的最长匹配长度\(mx[i]\). 因为要去重,对\(T\ ...

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

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

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

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

  7. UOJ #395 BZOJ 5417 Luogu P4770 [NOI2018]你的名字 (后缀自动机、线段树合并)

    NOI2019考前做NOI2018题.. 题目链接: (bzoj) https://www.lydsy.com/JudgeOnline/problem.php?id=5417 (luogu) http ...

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

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

  9. 洛谷P2178 [NOI2015]品酒大会(后缀自动机 线段树)

    题意 题目链接 Sol 说一个后缀自动机+线段树的无脑做法 首先建出SAM,然后对parent树进行dp,维护最大次大值,最小次小值 显然一个串能更新答案的区间是\([len_{fa_{x}} + 1 ...

随机推荐

  1. Mongo 分组后排序取时间最大的一整条数据对象

    db.getCollection('product_protocol_new').aggregate([ {$sort:{"end_date":-1}}, {$group:{ _i ...

  2. loader与plugin,module与chunk,compiler与compilation

    loader将各类型的文件转为webpack能处理的有效模块(module) 插件处理范围更广的任务,例如打包优化.压缩等 module程序的离散功能块,一个文件对应一个module chunk若干m ...

  3. android 内存泄漏问题【转】

    本文转载自:http://www.voidcn.com/article/p-hbnuyfwz-ee.html 内存泄露问题在一些压力测试的场景很容易暴露,例如一些常用应用场景反复操作(eg:反复切换前 ...

  4. CodeForces - 540C Ice Cave —— BFS

    题目链接:https://vjudge.net/contest/226823#problem/C You play a computer game. Your character stands on ...

  5. UVA10294 Arif in Dhaka (First Love Part 2) —— 置换、poyla定理

    题目链接:https://vjudge.net/problem/UVA-10294 题解: 白书P146~147. 为什么旋转i个间距,就有gcd(i,n)个循环,且每个循环有n/gcd(i,n)个元 ...

  6. c 获取 域名ip

    #include <stdio.h> #include <netdb.h> int main(int argc, char *argv[]) { ) { printf(]); ...

  7. (4)获取servlet常用api

    *五)与ServletAPI解耦 方式1 AddAction public String execute() throws Exception, IOException{ //获取请求对象reques ...

  8. 分享知识-快乐自己:SpringMVC 底层执行原理解析

    底层实现原理图: 观看底层代码: 1):打开 web.xml 文件  2):按住 Ctrl + 鼠标左键 进入底层查看源码   3):按住 Ctrl+o 找到对应的方法doDispatch   5): ...

  9. tensorflow knn mnist

    # MNIST Digit Prediction with k-Nearest Neighbors #----------------------------------------------- # ...

  10. Git_学习_02_ 分支

    Git鼓励大量使用分支: 1.查看分支:git branch 2.创建分支:git branch <name> 3.切换分支:git checkout <name> 4.创建+ ...