传送门

我有种自己根本没学过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. Java for LeetCode 082 Remove Duplicates from Sorted List II

    Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only distinct numb ...

  2. P3231 [HNOI2013]消毒

    P3231 [HNOI2013]消毒 二维覆盖我们已经很熟悉了 扩展到三维,枚举其中较小的一维,这里定义为$a$ 以$a$为关键字状压,$1$表示该面全选 剩下的面和二维覆盖一样二分图匹配 如果还没接 ...

  3. 图解HTTP接口自动化测试框架使用

    Robot Framework是一款python语言编写,通用的功能自动化测试框架.它使用了比较易用的表格数据语法,基于关键字驱动测试,主要用来验收测试和验收测试驱动开发(ATDD). 运行RIDE, ...

  4. Zabbix监控华为交换机

    一.    监控交换机首先要在交换机开通snmp协议. 有两种方式开通,web界面,及交换机的配置界面 Web界面开通: 交换机配置界面 有web界面的,使用web界面相对简单,本项目就是用web界面 ...

  5. c# json 排版

    public static string PraseToJson(string json) { try { JsonSerializer s = new JsonSerializer(); JsonR ...

  6. gulp记录

    npm install gulp -g //全局安装gulp gulp -v //此处若有问题,配置环境变量,npm config get prefix得到路径 npm init //新建nodejs ...

  7. ACM学习历程—HDU1584 蜘蛛牌(动态规划 && 状态压缩 || 区间DP)

    Description 蜘蛛牌是windows xp操作系统自带的一款纸牌游戏,游戏规则是这样的:只能将牌拖到比她大一的牌上面(A最小,K最大),如果拖动的牌上有按顺序排好的牌时,那么这些牌也跟着一起 ...

  8. configured to save RDB snapshots, but is currently not able to persist o...

    Redis问题 MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on d ...

  9. 洛谷P1352——动规

    题目:https://www.luogu.org/problemnew/show/P1352 代码如下: #include<iostream> #include<cstdio> ...

  10. Ubuntu ssh免密登录

    ssh免密登录工作原理 server A免登录到server B: 1.在A上生成公钥私钥. 2.将公钥拷贝给server B,要重命名成authorized_keys(从英文名就知道含义了) 3.S ...