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\)
魔改:處理出每個點的\(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\)集合可以用可持久化線段樹合併。
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;
}