LOJ2720. 「NOI2018」你的名字
给出一个字符串\(S\),然后有若干次询问,每次统计字符串\(T\)中出现过的\(S_{l..r}\)中没有出现过的子串的个数。
\(|S|,|T|\le 5*10^5,\sum |T|\le 10^6\)
自己想出来的做法:\(O(n\lg ^2 n)\),直接搞出SAM之后线段树+树上二分跳祖先。具体就是:对\(S\)建出SAM之后,离线枚举询问右端点。主要问题是对\(T\)的每个前缀求出作为\(S_{l..r}\)子串的最长后缀,即祖先中深度最大\(x\)满足\(mxr_x-len_x+1\ge l\)。显然可二分。
正解:\(O(n\lg n)\)。详细一点说明(有些部分上面的做法也需要用到):
同样是对\(S\)建出SAM,也是要对\(T\)的每个前缀求出作为\(S_{l..r}\)子串的最长后缀。求出来之后去重即可(对\(T\)建SAM,建出\(T\)的fail树,打标记表示其祖先不计入答案)。
对于这个问题,如果询问\(S_{1..|S|}\)就可以直接在SAM上跑。现在限制了范围,魔改一下跑的方式:(假设当前点为\(p\),匹配的长度为\(len\),新增的字符为\(c\))
原来:如果存在\(p.trans(c)\)则\(p\)跳过去且\(len\leftarrow len+1\),否则跳\(fail\),且\(len\leftarrow p.fail.len\)。(细致一些:否则\(len\leftarrow len-1\),如果\(len=p.fail\)则\(p\)跳\(fail\)。时间复杂度不变。便于推广)
魔改:处理出每个点的\(right\)集合。定义\(p.query(l,r)\)表示是否\(\exist x\in p.right,x\in [l,r]\)。如果\(p.trans(c).query(l+len,r)\)为真,则跳过去且\(len\leftarrow len+1\);否则\(len\leftarrow len-1\),如果\(len=p.fail\)则跳\(fail\))
可以如此说明它的正确性:当前已经配了合法的\(len\)长度,现在配\(len+1\),显然\(p.trans(c).query(l+len,r)\)为必要条件,并且根据\(right\)集合的定义得知这也是充分的。所以这是充分必要条件。
处理\(right\)集合可以用可持久化线段树合并。
using namespace std;
#include <bits/stdc++.h>
#define N 500005
#define ll long long
int n,m;
char s[N],t[N];
struct SAM{
struct Node{
Node *c[26],*fa;
int len;
} d[N*2],*S,*T;
int id(Node *x){return x-d;}
int cnt;
Node *newnode(){
++cnt;
memset(&d[cnt],0,sizeof d[cnt]);
return &d[cnt];
}
void init(){
T=S=&d[cnt=1];
memset(&d[1],0,sizeof d[1]);
}
void insert(int ch){
Node *nw=newnode(),*p,*q;
nw->len=T->len+1;
for (p=T;p && !p->c[ch];p=p->fa)
p->c[ch]=nw;
if (!p)
nw->fa=S;
else{
q=p->c[ch];
if (p->len+1==q->len)
nw->fa=q;
else{
Node *clone=newnode();
memcpy(clone,q,sizeof *q);
clone->len=p->len+1;
for (;p && p->c[ch]==q;p=p->fa)
p->c[ch]=clone;
nw->fa=q->fa=clone;
}
}
T=nw;
}
void build(char *s){
init();
for (;*s;++s)
insert(*s-'a');
}
} S,T;
struct Seg{
Seg *l,*r;
} d[N*50],*null,*rt[N*2];
int cnt;
Seg *newnode(){return &(d[++cnt]={null,null});}
void insert(int x,Seg *&t,int l=1,int r=n){
if (t==null) t=newnode();
if (l==r) return;
int mid=l+r>>1;
if (x<=mid) insert(x,t->l,l,mid);
else insert(x,t->r,mid+1,r);
}
Seg *merge(Seg *a,Seg *b){
if (a==null) return b;
if (b==null) return a;
Seg *c=newnode();
c->l=merge(a->l,b->l);
c->r=merge(a->r,b->r);
return c;
}
bool query(int st,int en,Seg *t,int l=1,int r=n){
if (t==null) return 0;
if (st<=l && r<=en) return 1;
int mid=l+r>>1;
bool res=0;
if (st<=mid) res=query(st,en,t->l,l,mid);
if (mid<en && !res) res=query(st,en,t->r,mid+1,r);
return res;
}
struct EDGE{
int to;
EDGE *las;
} e[N*2];
int ne;
EDGE *last[N*2];
void link(int u,int v){
e[ne]={v,last[u]};
last[u]=e+ne++;
}
void dfsS(int x){
for (EDGE *ei=last[x];ei;ei=ei->las){
dfsS(ei->to);
rt[x]=merge(rt[x],rt[ei->to]);
}
}
void buildG(){
S.build(s+1);
null=d;
*null={null,null};
for (int i=1;i<=S.cnt;++i)
rt[i]=null;
SAM::Node *t=S.S;
for (int i=1;i<=n;++i){
t=t->c[s[i]-'a'];
insert(i,rt[S.id(t)]);
}
for (int i=2;i<=S.cnt;++i)
link(S.id(S.d[i].fa),i);
dfsS(1);
}
int bz[N*2],dep[N*2];
void buildT(){
T.build(t+1);
memset(last,0,sizeof(EDGE*)*(T.cnt+1));
ne=0;
for (int i=2;i<=T.cnt;++i){
link(T.id(T.d[i].fa),i);
dep[i]=T.d[i].len;
bz[i]=0;
}
bz[1]=0;
}
ll ans;
void dfsT(int x){
for (EDGE *ei=last[x];ei;ei=ei->las){
dfsT(ei->to);
bz[x]=max(bz[x],bz[ei->to]);
ans+=max(dep[ei->to]-max(bz[ei->to],dep[x]),0);
}
}
int main(){
freopen("name.in","r",stdin);
freopen("name.out","w",stdout);
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
scanf("%s",s+1);
n=strlen(s+1);
buildG();
int Q;
scanf("%d",&Q);
while (Q--){
int l,r;
scanf("%s%d%d",t+1,&l,&r);
m=strlen(t+1);
buildT();
SAM::Node *q=S.S,*p=T.S;
int len=0;
for (int i=1;i<=m;++i){
while (len && (!q->c[t[i]-'a'] || l+len>r || !query(l+len,r,rt[S.id(q->c[t[i]-'a'])]))){
--len;
if (len==q->fa->len)
q=q->fa;
}
if (q->c[t[i]-'a'] && query(l+len,r,rt[S.id(q->c[t[i]-'a'])]))
q=q->c[t[i]-'a'],len++;
p=p->c[t[i]-'a'];
bz[T.id(p)]=max(bz[T.id(p)],len);
}
ans=0;
dfsT(1);
printf("%lld\n",ans);
}
return 0;
}