抄题解的时候其实感觉都还挺套路的。
然而我还是抄了三天。
造$SAM$,线段树合并得到$endpos$集合。倍增定位到$[l,r]$代表的节点,对于某一个长度$L$,有$endpos\in [l+L,r]$的节点在$[l,r]$出现过。$T$的子串都是前缀的后缀,找一下$T$每个前缀最长的在$[l,r]$出现过的后缀,记它的长度为$L$。
到这儿都比较套路了。
一开始想倍增找,然而是$\log^2$的,而且并不能确定$L$也无法保证跳到的节点是否正确。
出门右转题解区,其实直接暴力跳就好。不断缩减$L$检验是否满足条件,若$L$不满足当前节点就跳$fa$。复杂度$O(|T|\log)$。
然后是去重的问题。
$SAM$本身就能解决本质不同的子串数量,对$T$造$SAM$,不考虑$L$就有$\sum len[i]-len[fa[i]]$。考虑上就给每个节点限制一个$ma$,表示该节点的串最小长度为$ma$,前面求的$L$求的是每个前缀的限制,就把$L$限制在含有前缀的节点上,复制点也要把$ma$复制过去。答案就为$\sum \max\{len[i]-\max(len[fa[i]],ma[i]),0\}$(可能有负数对$0$取$\max$)
代码:
//以下所有比普通SAM多了个t的都是T的SAM
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#define maxn 1000005
#define inf 0x3f3f3f3f
using namespace std;
inline int read(){
int x=0,y=0;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')y=1;ch=getchar();}
while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
return y?-x:x;
}
#define ls(x) ls[x]
#define rs(x) rs[x]
#define son(x,y) son[x][y]
#define sont(x,y) sont[x][y]
int ls[maxn*25],rs[maxn*25],h[maxn],num,all,n;
int son[maxn][26],fa[maxn],len[maxn],root[maxn],w[maxn]={1},last=1,cnt=1;
int sont[maxn][26],fat[maxn],lent[maxn],ma[maxn],lastt,cntt;
char s[maxn];
struct edge{
int pre,to;
}e[maxn];
inline void add(int from,int to){
e[++num].pre=h[from],h[from]=num,e[num].to=to;
}
void modify(int poi,int l,int r,int &node){
node=++all;
if(l==r)return;
int mid=l+r>>1;
if(poi<=mid)modify(poi,l,mid,ls(node));
else modify(poi,mid+1,r,rs(node));
}
int ask(int L,int R,int l,int r,int node){
if(!node)return 0;
if(L<=l&&R>=r)return 1;
int mid=l+r>>1;
if(L<=mid&&ask(L,R,l,mid,ls(node)))return 1;
if(R>mid&&ask(L,R,mid+1,r,rs(node)))return 1;
return 0;
}
int merge(int x,int y){
if(!x||!y)return x|y;
int ne=++all;
ls(ne)=merge(ls(x),ls(y));
rs(ne)=merge(rs(x),rs(y));
return ne;
}
void insert(int c){
int p=last,ne=last=++cnt;
modify(len[ne]=len[p]+1,1,n,root[ne]);
while(p&&!son(p,c))son(p,c)=ne,p=fa[p];
if(!p)fa[ne]=1;
else {
int q=son(p,c);
if(len[q]==len[p]+1)fa[ne]=q;
else {
int sp=++cnt;
memcpy(son[sp],son[q],sizeof son[q]);
len[sp]=len[p]+1,fa[sp]=fa[q],fa[q]=fa[ne]=sp;
while(p&&son(p,c)==q)son(p,c)=sp,p=fa[p];
}
}
}
void dfs(int node=1){
int x;
for(register int i=h[node];i;i=e[i].pre)
x=e[i].to,dfs(x),root[node]=merge(root[node],root[x]);
}
inline void init(){
memset(sont[1],0,sizeof sont[1]);
lastt=1,cntt=1;
}
long long calc(){
long long ans=0;
for(register int i=2;i<=cntt;++i)
ans+=max(0,lent[i]-max(lent[fat[i]],ma[i]));
return ans;
}
void insertt(int c,int d){
int p=lastt,ne=lastt=++cntt;
memset(sont[ne],0,sizeof sont[ne]);
lent[ne]=lent[p]+1,ma[ne]=d;
while(p&&!sont(p,c))sont(p,c)=ne,p=fat[p];
if(!p)fat[ne]=1;
else {
int q=sont(p,c);
if(lent[q]==lent[p]+1)fat[ne]=q;
else {
int sp=++cntt;
memcpy(sont[sp],sont[q],sizeof sont[q]);
ma[sp]=ma[q],lent[sp]=lent[p]+1,fat[sp]=fat[q],fat[q]=fat[ne]=sp;
while(p&&sont(p,c)==q)sont(p,c)=sp,p=fat[p];
}
}
}
int main(){
scanf("%s",s+1),n=strlen(s+1);
int m,l,r,node,N,L;
for(register int i=1;i<=n;++i)insert(s[i]-'a');
for(register int i=1;i<=n;++i)w[i]=son(w[i-1],s[i]-'a');
for(register int i=2;i<=cnt;++i)add(fa[i],i);
dfs();
m=read();
while(m--){
scanf("%s",s+1),N=strlen(s+1),l=read(),r=read();
init(),node=1,L=0;
for(register int i=1;i<=N;++i){
if(son(node,s[i]-'a')&&ask(l+L,r,1,n,root[son(node,s[i]-'a')]))node=son(node,s[i]-'a'),++L;
else {
while(L&&(!son(node,s[i]-'a')||!ask(l+L,r,1,n,root[son(node,s[i]-'a')]))){
--L;
if(L==len[fa[node]])node=fa[node];
}
if(!son(node,s[i]-'a')||!ask(l+L,r,1,n,root[son(node,s[i]-'a')]))node=1,L=0;
else node=son(node,s[i]-'a'),++L;
}
insertt(s[i]-'a',L);
}
printf("%lld\n",calc());
}
}
一些闲扯:
关于这道题
它让我写了一个星期,在看了题解后依然抄了三天
让我在二轮后不想去机房,因为一去机房就要继续写这屑题
让我丧失学OI的信心
让我决定立刻结束字符串的学习
让我对曾经认为一辈子都不会学的计算几何产生了兴趣
不过其实这道题对$SAM$的能有更深入的认识吧,也涨了不少姿势。
而且在$loj$上交的时候出现了一个神奇的问题:因为编译出的$exe$过大($9MB$,我也不知道为啥会这么大啊$QAQ$)不支持运行导致$CE$?
喵喵喵?