后缀自动机学习笔记(应用篇)
command_block · · 个人记录
请配合 后缀自动机学习笔记(干货篇) 与 题单:SAM练习题 食用。
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
4. \rm SAM 的初级应用
历经千辛万苦构造出了
众所周知,
首先建立出文章串的
代码就不放了,毕竟太模板了……
比较套路的一道题目。
- 思路1:
\rm SAM 是个DAG,于是乎可以在上面DP。(不常用)
一般来讲,DAG上可能重复转移,是很难跑计数DP的。
但是我们知道后缀自动机的性质 : 任意两个节点的表示集合没有交。
所以,从某个节点(不一定要求是源点)出发的路径组成的串,都是互不相同的,如果相同的话,就违背了上述性质。
所以我们只要统计路径数即可,不需要考虑重复问题。
设
DP的时候令
不过题目中不算空串,那么
Code:
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#define MaxN 100500
using namespace std;
int n;
char str[MaxN];
struct Node
{int t[26],f,len;}
a[MaxN<<1];
int las,tn;
void add(int c)
{
int p=las,np=++tn;las=np;
a[np].len=a[p].len+1;
for (;p&&!a[p].t[c];p=a[p].f)a[p].t[c]=np;
if (!p)a[np].f=1;//case 1
else {
int v=a[p].t[c];
if (a[v].len==a[p].len+1)a[np].f=v;//case 2
else {
int nv=++tn;a[nv]=a[v];
a[nv].len=a[p].len+1;
for (;p&&a[p].t[c]==v;p=a[p].f)a[p].t[c]=nv;
a[np].f=a[v].f=nv;//case 3
}
}
}
long long f[MaxN<<1];
void dfs(int u)
{
if (f[u])return ;
for (int i=0,v;i<26;i++)
if (v=a[u].t[i])
{dfs(v);f[u]+=f[v];}
f[u]++;
}
int main()
{
scanf("%d%s",&n,str);las=tn=1;
for (int i=0;i<n;i++)add(str[i]-'a');
dfs(1);
printf("%lld",f[1]-1);
return 0;
}
- 思路2:
\rm SAM 每个节点表示的串没有交集,而且一定表示了所有的串。
那我们把所有节点表示的串的个数(类大小)加起来就好了,
考虑到
这种方法需要对
Code:
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#define MaxN 100500
using namespace std;
int n;
char str[MaxN];
struct Node
{int t[26],f,len;}
a[MaxN<<1];
int las,tn;
void add(int c)
{
int p=las,np=++tn;las=np;
a[np].len=a[p].len+1;
for (;p&&!a[p].t[c];p=a[p].f)a[p].t[c]=np;
if (!p)a[np].f=1;//case 1
else {
int v=a[p].t[c];
if (a[v].len==a[p].len+1)a[np].f=v;//case 2
else {
int nv=++tn;a[nv]=a[v];
a[nv].len=a[p].len+1;
for (;p&&a[p].t[c]==v;p=a[p].f)a[p].t[c]=nv;
a[np].f=a[v].f=nv;//case 3
}
}
}
int main()
{
scanf("%d%s",&n,str);las=tn=1;
for (int i=0;i<n;i++)add(str[i]-'a');
long long ans=0;
for (int i=1;i<=tn;i++)ans+=a[i].len-a[a[i].f].len;
printf("%lld",ans);
return 0;
}
我们知道每个点代表着一个
显然有 : siz ) 就是这点包含的一堆字子串(在原串中)的出现次数。
如何统计 siz 呢?我们考虑DP:
先把每个前缀所属的点的 siz 设为
称这些点为前缀节点,每个前缀节点代表一个
( 事实上,有且仅有某个前缀节点的祖先拥有该
然后 u.siz+=son.siz ,子树求和即可。
这需要把树建出来,常数较大。
另外一种常数更小的方法是:先按照
len排序,然后len大的先贡献。由于
fa.len>u.len 正确性显然。这个排序如果用
std::sort的话复杂度反而会带log ,那就得不偿失了。观察到所有的
len\leq n ,不妨使用基数排序。但是这种方法有一定的局限性,比如说广义
\rm SAM 用不了,某些时候操作麻烦等等……
我们也维护了某个点的最长串 len ,统计
Code:
这里只提供树形dp版,基排版可以到其他题目里面找。
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#define ll long long
#define MaxN 1005000
using namespace std;
struct Node
{int t[26],f,len,siz;}
a[MaxN<<1];
int tn,las;
void add(int c)
{
int p=las,np=++tn;las=np;
a[np].len=a[p].len+1;
for (;p&&!a[p].t[c];p=a[p].f)a[p].t[c]=np;
if (!p)a[np].f=1;
else {
int v=a[p].t[c];
if (a[v].len==a[p].len+1)a[np].f=v;
else {
int nv=++tn;a[nv]=a[v];
a[nv].len=a[p].len+1;
for (;p&&a[p].t[c]==v;p=a[p].f)a[p].t[c]=nv;
a[np].f=a[v].f=nv;
}
}
}
vector<int> g[MaxN<<1];
ll ans;
void dfs(int u)
{
for (int i=0,v;i<g[u].size();i++){
dfs(g[u][i]);
a[u].siz+=a[g[u][i]].siz;
}
if (a[u].siz>1)
ans=max(ans,1ll*a[u].siz*a[u].len);
}
int n;
char str[MaxN];
int main()
{
scanf("%s",str+1);
n=strlen(str+1);
las=tn=1;
for (int i=1;i<=n;i++)add(str[i]-'a');
for (int i=1,p=1;i<=n;i++){
p=a[p].t[str[i]-'a'];
a[p].siz=1;
}for (int i=2;i<=tn;i++)
g[a[i].f].push_back(i);
dfs(1);
printf("%lld\n",ans);
return 0;
}
首先这个翻译非常的假,人话题意:本质不同子串出现次数的平方和
对于
评测记录
题意 : 不断push_back字符,每次统计本质不同子串数。
( 这道题字符集很大,需要使用 std::map 建立后缀自动机 )
上文
那么每次有
首先新建 np 的时候,ans+=a[np].len;
然后对于case 1,2,我们没有新建节点,只是加入了一条f指针,所以ans-=a[a[np].f].len
对于case 3,我们新建了 nv ,要使ans+=a[nv].len;,
但是它只有两个儿子 v 和 np ,又需要ans-=2*a[nv].len;
又考虑到a[np].f=nv,其实还是ans-=a[a[np].f].len;
综上,每次add之后令ans+=a[np].len-a[a[np].f].len;即可。
评测记录
-
[P3975 [TJOI2015]弦论](https://www.luogu.org/problemnew/show/P3975)
细节比较的多TAT
我们考虑使用类似线段树上二分的办法,在
已经到达了某个点,我们从 a~z 枚举出边,选定后,当前剩余的
注意在开始选择路径之前,还得要减去一些东西(详见代码)……
问题就在于减掉多少。设
这很好办, dp出来。
开始麻烦了……我们知道一个子串
那么我们就是要统计DAG上 DAG内,
也就是每一个串(路径)的贡献是 : 终点的 siz。
采用siz即可。
那么如何统计呢?这也好办,siz就可以了。
如何判断无解?这道题同样不计空串,我们要在
注意其他点空串的贡献是不能去掉的,因为空串表示停止在该点。
评测记录
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
5. \rm SAM 与AC自动机的相似性
回忆 : 自动机某个点表示的串,是指从根到该点的路径构成的所有串。
众所周知,AC自动机的 fail 指针指向的地方,是该节点表示的串的最长后缀,也就是把前面去掉(尽可能少的)一点。
我们注意到 fa指针有同样的性质 : 儿子是由父亲在前面加字符产生的,那么父亲就可以视作在儿子前面去掉字符。
我们完全可以把
这有什么用呢? 来看一下例题吧:
我们把两个串分别称作
对于
int p=1,plen=0;//当前点,匹配长度 for (int i=0,c;i<len2;i++){ c=s2[i]-'a'; if (!a[p].t[c]){ while(p!=1&&!a[p].t[c])p=a[p].f; plen=a[p].len; //如果没有转移边,不断跳fail } if (a[p].t[c]){ p=a[p].t[c];plen++; //能匹配,plen++ }slen[i]=plen; } int ans=0; for (int i=0;i<len2;i++)ans=max(ans,slen[i]); printf("%d",ans); return 0; }
## 5.2 [SP1812 LCS2 - 多串最长公共子串](https://www.luogu.org/problem/SP1812)
类似地,把第一个串当做匹配串,其他的串建立 $\rm SAM$
把第一个串在每个 $\rm SAM$ 上跑匹配,$slen$ 对当前匹配长度取$\min$。
最终回答整个 $slen$ 的 $\max$。
代码奇短无比,比SA+单队不知高到哪里去了。
[AC记录](https://www.luogu.org/record/22923336)
## 5.3 [CF235C Cyclical Quest](https://www.luogu.org/problem/CF235C)
题意 : 给出一个文本串和若干询问串,求每个询问串的所有本质不同循环,能够匹配到的次数。
先不考虑本质不同这件事。
循环就是把询问串第一个字符拿去放在最后面。
可以视为,先把目前匹配到的串的第一个字符删掉,然后再加上一个。
如果目前这个串根本没有在文本中完整出现,那么这个删除操作可以忽略。
否则,`len--`,如果删了前面之后 $len$ **不在当前节点区间**($(u.fa.len,u.len]$)里面了就跳 `f`。
(在前面删掉字符?这不就是parent tree跳`f`的性质吗!)
加字符的操作同上。
(~~太有趣了,双端队列?很有出题价值![笑]~~)
最后把匹配到的所有节点 $siz$ 加在一起就好了。
还要考虑去重问题,这也好办 : 如果同一个询问串多次匹配到同一个节点,贡献只算一次,具体可以打标记实现。
```cpp
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#define MaxN 1000500
using namespace std;
struct Node
{int t[26],len,f,siz;}a[MaxN<<1];
int tn,las;
void add(int c)
{
int p=las,np=++tn; las=np;
a[np].len=a[p].len+1;
for (;p&&!a[p].t[c];p=a[p].f)a[p].t[c]=np;
if (!p)a[np].f=1;
else {
int v=a[p].t[c];
if (a[v].len==a[p].len+1)a[np].f=v;
else {
int nv=++tn;a[nv]=a[v];
a[nv].len=a[p].len+1;
for (;p&&a[p].t[c]==v;p=a[p].f)a[p].t[c]=nv;
a[np].f=a[v].f=nv;
}
}
}
vector<int> g[MaxN<<1];
void dfs(int num)
{
for (int i=0;i<g[num].size();i++){
dfs(g[num][i]);
a[num].siz+=a[g[num][i]].siz;
}
}
char str[MaxN];int len;
int vis[MaxN<<1],T;
void solve()
{
scanf("%s",str+1);
len=strlen(str+1);
int slen=0,p=1;
for (int i=1;i<=len;i++){
str[i]-='a';
while(p>1&&!a[p].t[str[i]])
slen=a[p=a[p].f].len;
if (a[p].t[str[i]]){
p=a[p].t[str[i]];
slen++;
}
}long long ans=0;
for (int i=1;i<=len;i++){
if (slen==len){
if (vis[p]!=T)ans+=a[p].siz;
vis[p]=T;
if (--slen==a[a[p].f].len)
p=a[p].f;
}while(p>1&&!a[p].t[str[i]])
slen=a[p=a[p].f].len;
if (a[p].t[str[i]]){
p=a[p].t[str[i]];
slen++;
}
}printf("%I64d\n",ans);
}
int main()
{
scanf("%s",str+1);
len=strlen(str+1);
tn=las=1;
for (int i=1;i<=len;i++)add(str[i]-='a');
for (int i=2;i<=tn;i++)g[a[i].f].push_back(i);
for (int i=1,p=1;i<=len;i++)
a[p=a[p].t[str[i]]].siz=1;
dfs(1);
scanf("%d",&T);T++;
while(--T)solve();
return 0;
}
5.4 P6640 [BJOI2020] 封印
先建立
对于区间
可以二分答案
若满足这个要求,那么
复杂度
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
6. \rm SAM 构建后缀树
广义
构造方法奇为暴力 :
每次加入一个串前,把las设为1。
正确性显然(雾)
补记 : 我naive了,Trie树上正牌的广义SAM一点都不会,枯了……
例题 : SP8093 JZPGYZ - Sevenk Love Oimaster
首先对于所有模板串建立广义 siz。
对于匹配串在 siz。
怎么求呢? 我们对第
附 : CF427D Match & Catch (近乎广义
还有一个奇怪的题 : Loj#6071. 「2017 山东一轮集训 Day5」字符串
先考虑单个串怎么做,前面我们讲过了可以
对于多个子串拼接的情况,我们无法构建类似
我们考虑先对每个串单独建立
有考虑另一种能跳跃拼接的字符串数据结构 : 子序列自动机。
其做法就是从每个位置'c'的出边向后连接到最近的一个'c'。
那么,我们考虑把这个若干个
如果某个点没有'c'出边,那么连向后面第一个满足要求的的DAG源点的'c'出边。
引用某位大佬的话 :
现在我们建出来的DAG就非常牛逼了,如果发现想走某一条转移边而没有办法走的时候,它会自动跳到下一个字符串上去,我们在这张DAG上求一下路径总数就是答案了。
看起来很有出题价值。
#include<algorithm>
#include<cstring>
#include<cstdio>
#define mod 1000000007
#define sf scanf
#define pf printf
#define MaxN 1005000
using namespace std;
struct Data
{int f,len,t[26];}a[MaxN<<1];
int tn,las,rt[MaxN],frt;
void add(int c)
{
int np=++tn,p=las;las=np;
a[np].len=a[p].len+1;
for (;p&&!a[p].t[c];p=a[p].f)a[p].t[c]=np;
if (!p)a[np].f=frt;
else {
int v=a[p].t[c];
if (a[v].len==a[p].len+1)a[np].f=v;
else {
int nv=++tn;a[nv]=a[v];
a[nv].len=a[p].len+1;
for (;p&&a[p].t[c]==v;p=a[p].f)a[p].t[c]=nv;
a[v].f=a[np].f=nv;
}
}
}
long long dp[MaxN<<1];
void dfs(int u)
{
if (dp[u])return ;
for (int k=0,v;k<26;k++)
if (v=a[u].t[k]){
dfs(v);
dp[u]+=dp[v];
}
dp[u]=(dp[u]+1)%mod;
}
int n,tp[26];
char str[MaxN];
int main()
{
sf("%d",&n);
for (int i=1,len;i<=n;i++){
sf("%s",str);
rt[i]=frt=las=++tn;
len=strlen(str);
for (int j=0;j<len;j++)
add(str[j]-'a');
}rt[n+1]=tn+1;
for (int i=n;i;i--){
for (int j=rt[i];j<rt[i+1];j++)
for (int k=0;k<26;k++)
if (!a[j].t[k])
a[j].t[k]=tp[k];
for (int k=0;k<26;k++)
tp[k]=a[rt[i]].t[k];
}dfs(1);
pf("%lld",dp[1]);
return 0;
}
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
8.线段树合并维护edp
我们发现
我们可以考虑使用线段树合并来维护
例题 : CF1037H Security
题意 : 给一个文章串
截取
- 我们先考虑没有截取限制,即
S2=S 。
那么,有一个显而易见的贪心:
先建立
找一个大于当前
问题在于可能候选的字符都是小于
如果退到源点还是没有方案,那么输出-1。
具体的复杂度是
- 现在考虑截取限制,即
S2=S[l...r] 。
如果我们知道
还是要先建立
那么我们发现,加入我们把所有的“不好点”以及相关的边删掉,那么我们就得到了
这玩意虽然不保证点数边数为
我们考虑设计一个函数,能够判定某个点是否为好点,那样的话就可以实现上述贪心操作了。
如何判断呢?我们采用线段树合并来维护每个点的
(当然用普通平衡树启发式合并也是可以的,不过代码长,复杂度还多个
我们只要在线段树上区间查询一下
那么复杂度就是
Code:
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#define MaxN 200500
using namespace std;
int n,q;
char str[MaxN],st[MaxN];
struct $\rm SAM$ _Node
{int t[26],f,len,edp,rt;}
a[MaxN<<1];
int las,tn;
void add(int c)
{
int p=las,np=++tn;las=np;
a[np].len=a[p].len+1;
for (;p&&!a[p].t[c];p=a[p].f)a[p].t[c]=np;
if (!p)a[np].f=1;//case 1
else {
int v=a[p].t[c];
if (a[v].len==a[p].len+1)a[np].f=v;//case 2
else {
int nv=++tn;a[nv]=a[v];
a[nv].len=a[p].len+1;
for (;p&&a[p].t[c]==v;p=a[p].f)a[p].t[c]=nv;
a[np].f=a[v].f=nv;//case 3
}
}
}
vector<int> g[MaxN<<1];
struct SGT_Node
{int l,r;}b[MaxN*41];
int tot;
int marge(int x,int y)
{
if (!x||!y)return x|y;
int pos=++tot;
b[pos].l=marge(b[x].l,b[y].l);
b[pos].r=marge(b[x].r,b[y].r);
return pos;
}
int to;
void add(int l,int r,int &num)
{
if (!num)num=++tot;
if (l==r)return ;
int mid=(l+r)>>1;
if (to<=mid)add(l,mid,b[num].l);
else add(mid+1,r,b[num].r);
}
int wfl,wfr;
bool query(int l,int r,int num)
{
if (!num)return 0;
if (wfl<=l&&r<=wfr)return 1;
int mid=(l+r)>>1;
if (wfl<=mid&&query(l,mid,b[num].l))return 1;
if (mid<wfr&&query(mid+1,r,b[num].r))return 1;
return 0;
}
bool check(int num,int len)
{
bool ans;
wfl+=len-1;
if (wfl>wfr)ans=0;
else ans=query(1,n,a[num].rt);
wfl-=len-1;
return ans;
}
void dfs(int num)
{
if (a[num].edp){
to=a[num].edp;
add(1,n,a[num].rt);
}
for (int i=0,v;i<g[num].size();i++){
dfs(v=g[num][i]);
a[num].rt=marge(a[num].rt,a[v].rt);
}
}
int sp[MaxN];
int main()
{
scanf("%s%d",str,&q);las=tn=1;
n=strlen(str);
for (int i=0;i<n;i++)add(str[i]-'a');
for (int i=0,p=1;i<n;i++){
p=a[p].t[str[i]-'a'];
a[p].edp=i+1;
}for (int i=2;i<=tn;i++)
g[a[i].f].push_back(i);
dfs(1);
for (int qt=1,p,sav,len;qt<=q;qt++){
scanf("%d%d%s",&wfl,&wfr,st);
len=strlen(st);sp[sav=0]=p=1;
for (int i=0;i<len;i++)st[i]-='a';
for (int i=0,v;i<len;i++){
v=a[p].t[st[i]];
if (v&&check(v,i))sp[sav=i+1]=p=v;
else break;
}
st[len]=-1;
char las=100;
for (;las==100&&sav>=0;sav--){
p=sp[sav];
for (int j=st[sav]+1,v;j<26;j++)
if ((v=a[p].t[j])&&check(v,sav+1))
{las=j;break;}
}
if (las==100)puts("-1");
else {
for (int i=0;i<=sav;i++)putchar(st[i]+'a');
putchar(las+'a');putchar('\n');
}
}return 0;
}
- P4094 [HEOI2016/TJOI2016]字符串
先把原串倒过来,现在问题变成了
考虑二分一个
令
建立
然后定位包含子串 len 在
定位到之后,在当前点看一看有没有
复杂度
评测记录
- SP687 REPEATS - Repeats
结论 : 若
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
9.SAM上树分治
当你要统计前缀对子之间的贡献时,你会发现往往与
而这又能转化成关于LCA的一些信息,那么我们就可以使用有根树分治来统计。
某些时候也可以直接树剖。
-
[DS记录]Loj#6198. 谢特 (启发式合并01Trie)
-
[DS记录]P5161 WD与数列 (启发式合并线段树)
-
[DS记录]P4482 [BJWC2018]Border 的四种求法 (重链剖分)
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
10.技巧大杂烩
10.1 P4081 [USACO17DEC]Standing Out from the Herd
设这些串分别为
然后,利用
对于
最终可以得到每个串的每个后缀在其他串上的最长匹配长度。
最后对于每个串单独建立
具体来讲,就是先随意求出每个点的任意一个
评测记录
10.2 P4770 [NOI2018]你的名字
题意: 给出一个文本串和若干询问串,求该询问串没有在文本串某个区间中匹配的本质不同子串个数。
首先弱化一下,不考虑那个区间限制。
做法和上一题差不多,先跑个后缀匹配,然后再对询问串建立
问题在于现在加上了区间限制,按照套路,先线段树合并弄出
考虑一下我们匹配的时候究竟在做什么:
-
跑匹配,也就是要知道能不能匹配,以及下一步的出边是什么。
-
读取
len,这样才知道究竟匹配了多长。 -
发现没有出边,跳
parent 树。
我们要借用原有的
在匹配的时候,我们直接取整个
注意!失配了之后不能直接跳f,把匹配的长度减一后,还得再试一次,直到该节点的
怎么读取
考虑到原有
现在要求这些子串必须在某个区间内,那么这个长度区间的上界就要对某个东西取
而下界是不会变的,所以比较下界(
至于跳f这件事,直接在原自动机上跳就好了,可以证明复杂度是正确的(每跳一次匹配长度至少减一),而且不会丢信息。
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#define MaxN 1005000
using namespace std;
struct SAM_Node
{int t[26],f,len,edp,rt;};
int wfl,wfr,to;
long long ans=0;
struct SAM
{
char str[MaxN];
SAM_Node a[MaxN<<1];
int las,tn,n;
void add(int c)
{
int p=las,np=++tn;las=np;
a[np].len=a[p].len+1;
for (;p&&!a[p].t[c];p=a[p].f)a[p].t[c]=np;
if (!p)a[np].f=1;//case 1
else {
int v=a[p].t[c];
if (a[v].len==a[p].len+1)a[np].f=v;//case 2
else {
int nv=++tn;a[nv]=a[v];
a[nv].len=a[p].len+1;
for (;p&&a[p].t[c]==v;p=a[p].f)a[p].t[c]=nv;
a[np].f=a[v].f=nv;//case 3
}
}
}
void buildSAM()
{
scanf("%s",str);
n=strlen(str);las=tn=1;
for (int i=0;i<n;i++)add(str[i]-'a');
for (int i=0,p=1;i<n;i++){
p=a[p].t[str[i]-'a'];
a[p].edp=i+1;
}for (int i=1;i<=tn;i++)g[i].clear();
for (int i=2;i<=tn;i++)
g[a[i].f].push_back(i);
}
void dfsedp(int num)
{
for (int i=0,v;i<g[num].size();i++){
dfsedp(v=g[num][i]);
a[num].edp=max(a[num].edp,a[v].edp);
}
}
void build1()
{buildSAM();dfsedp(1);}
struct SGT_Node
{int l,r,x;}b[MaxN*42];
int tot;
int marge(int x,int y)
{
if (!x||!y)return x|y;
int pos=++tot;
b[tot].x=max(b[x].x,b[y].x);
b[pos].l=marge(b[x].l,b[y].l);
b[pos].r=marge(b[x].r,b[y].r);
return pos;
}
void add(int l,int r,int &num)
{
if (!num)num=++tot;
b[num].x=max(b[num].x,to);
if (l==r)return ;
int mid=(l+r)>>1;
if (to<=mid)add(l,mid,b[num].l);
else add(mid+1,r,b[num].r);
}
vector<int> g[MaxN<<1];
void dfs(int num)
{
if (a[num].edp){
to=a[num].edp;
add(1,n,a[num].rt);
}
for (int i=0,v;i<g[num].size();i++){
dfs(v=g[num][i]);
a[num].rt=marge(a[num].rt,a[v].rt);
}
}
void build2()
{buildSAM();tot=0;dfs(1);}
int query(int l,int r,int num)
{
if (!num)return 0;
if (wfl<=l&&r<=wfr)return b[num].x;
int mid=(l+r)>>1,ans=0;
if (mid<wfr)ans=max(ans,query(mid+1,r,b[num].r));
if (!ans&&wfl<=mid)ans=max(ans,query(l,mid,b[num].l));
return ans;
}
bool check(int num,int len)
{
int ans;
wfl+=len-1;
if (wfl>wfr)ans=0;
else ans=query(1,n,a[num].rt);
wfl-=len-1;
return ans>0;
}
void clac(int *slen)
{
ans=0;
for (int i=2;i<=tn;i++)
if (slen[a[i].edp]<a[i].len)
ans+=a[i].len-max(slen[a[i].edp],a[a[i].f].len);
}
void clear()
{
for (int i=1;i<=tn;i++){
for (int j=0;j<26;j++)
a[i].t[j]=0;
a[i].f=a[i].len=a[i].edp=0;
}
}
}S,T;
int q,slen[MaxN];
int main()
{
S.build2();
scanf("%d",&q);
while(q--){
T.build1();
scanf("%d%d",&wfl,&wfr);
int p=1,len=0;
for (int i=0,v;i<T.n;i++){
v=T.str[i]-'a';
while(1){
if (S.check(S.a[p].t[v],len+1))
{p=S.a[p].t[v];len++;break;}
else {
if (p==1){len=0;break;}
len--;
if (len==S.a[S.a[p].f].len)
p=S.a[p].f;
}
}slen[i+1]=len;
}
T.clac(slen);
printf("%lld\n",ans);
T.clear();
}return 0;
}
- 题解 CF1098F 【Ж-function】