「NOI2018」 你的名字
怕是以後在也不能做非思維題了,要不然都想不出來啥了
其實早知道是線段樹合併,然後拍上一個維護 \(endpos\) 就開始坐以待斃了
Description
給定 \(S\) 和若干個詢問,每個詢問是 \(T,l,r\) 的形式,求 \(T\) 有多少個子串在 \(S[l\dots r]\) 中沒有出現過
\(|S|\le 5\times 10^5,\sum |T|\le 10^6\)
其中對於 \(68pts\) 的部分分滿足 \(l=1,r=|S|\)
Solution
首先考慮一個 \(n^2\) 的做法是對於每個 \(T\) 建 \(SAM\) 然後跑可達性統計,然後兩個串一起跑,如果這個點在 \(T\)
貌似也沒啥優化的空間,那麼換一個思路,往 字尾樹 上面靠
直接統計還是不太行,那麼正難則反,用總的 \(T\) 上的減掉公共子串
具體就是求一下 \(l[i]\) 表示 \(T\) 到 \(i\) 的字首有多長可以在 \(S\) 上面作為一段字尾出現
如果如果不考慮存在重複的情況,就把 \(T\) 放到 \(S\) 上面跑,每次不行了就跳 \(fa\)
(這裡深刻一個觀點:字尾樹上面點的深度基本上是沒啥用的,含義就可以輕鬆得證)
因為 \(fa\) 裡面的都是 \(x\) 的字尾,又要選最大值,所以每跳一下,長度減成現在點的 \(len\)
那麼這就得到了一個 \(68pts\)
接著考慮怎麼做不是全串的情況,先求所有的 \(endpos\) 集合
(如果對字尾樹的相關熟練的話,其實這步是很好說的)
然後考慮對於 \([l\dots r]\) 的限制怎麼寫
然而並不能每次暴力重建,但是每次只是找兒子,跳父親,找當前點在 \([l,r]\) 內的最大 \(len\)
第一個其實對應的是 \(endpos\) 有沒有在 \([l,r]\) 之間有點
第二個是跳父親,因為父親的 \(endpos\) 是母集,隨便跳
最後一個比較噁心,想清楚了是 \(min(len[i],now-max(now,pos)+1)\)
\(pos\) 是指在 \([l,r]\) 內的最大的 \(endpos\)
還是一樣的跑就行了
本文中加粗的部分是瓶頸,觀察字尾自動機在什麼時候用和跑 \(pos\) 這兩個套路確實厲害
彷彿 \(CF\) 上有類似套路題?
Code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define reg register
namespace yspm{
inline int read(){
int res=0,f=1; char k;
while(!isdigit(k=getchar())) if(k=='-') f=-1;
while(isdigit(k)) res=res*10+k-'0',k=getchar();
return res*f;
}
const int N=5e5+10,M=1e6+10,SZ=M*40;
int ls[SZ],rs[SZ],sum[SZ],tot,rt[M];
inline int max(int x,int y){return x>y?x:y;}
struct Saffix_Automation{
int son[M][26],len[M],fa[M],tot,las,id[M];
inline void extend(int x,int pos,bool fl){
int tmp=las,np=las=++tot; len[np]=len[tmp]+1; id[np]=pos;
while(!son[tmp][x]) son[tmp][x]=np,tmp=fa[tmp];
if(!tmp) return fa[np]=1,void();
int q=son[tmp][x]; if(len[q]==len[tmp]+1) return fa[np]=q,void();
int clone=++tot; len[clone]=len[tmp]+1; fa[clone]=fa[q]; fa[q]=fa[np]=clone;
for(reg int i=0;i<26;++i) son[clone][i]=son[q][i]; if(fl) id[clone]=id[np];
while(son[tmp][x]==q) son[tmp][x]=clone,tmp=fa[tmp];
return ;
}
inline void clear(){
for(reg int i=1;i<=tot;++i) fa[i]=len[i]=id[i]=0,memset(son[i],0,sizeof(son[i])); las=tot=1;
return ;
}
inline void init(){tot=1; las=1; return;}
inline int num(int x){return len[x]-len[fa[x]];}
}S1,S2;
inline void push_up(int x){return sum[x]=sum[ls[x]]+sum[rs[x]],void();}
inline void upd(int &p,int l,int r,int pos){
if(!p) p=++tot;
if(l==r) return ++sum[p],void();
int mid=(l+r)>>1; if(pos<=mid) upd(ls[p],l,mid,pos); else upd(rs[p],mid+1,r,pos);
return push_up(p);
}
inline int merge(int x,int y,int l,int r){
if(!x||!y) return x+y;
if(l==r) return sum[x]|=sum[y],x;
int mid=(l+r)>>1,p=++tot;
ls[p]=merge(ls[x],ls[y],l,mid); rs[p]=merge(rs[x],rs[y],mid+1,r);
return push_up(p),p;
}
ll ans;
int head[M],cnt,mx[M],now,nowlen,len,slen; char s[M];
struct edge{int to,nxt;}e[M];
inline void add(int u,int v){
e[++cnt].to=v; e[cnt].nxt=head[u];
return head[u]=cnt,void();
}
inline void dfs(int x){
if(S1.id[x]) upd(rt[x],1,slen,S1.id[x]);
for(reg int i=head[x];i;i=e[i].nxt) dfs(e[i].to),rt[x]=merge(rt[x],rt[e[i].to],1,slen);
return ;
}
inline int query(int p,int l,int r,int st,int ed){
if(!p) return 0;
if(st<=l&&r<=ed) return sum[p];
int mid=(l+r)>>1;
if(st<=mid&&ls[p]&&query(ls[p],l,mid,st,ed)) return 1;
if(ed>mid&&rs[p]&&query(rs[p],mid+1,r,st,ed)) return 1;
return 0;
}
signed main(){
scanf("%s",s+1); slen=strlen(s+1); S1.init();
for(reg int i=1;i<=slen;++i) S1.extend(s[i]-'a',i,0);
for(reg int i=1;i<=S1.tot;++i) add(S1.fa[i],i); dfs(1);
int T=read(),l,r; while(T--){
S2.clear(); scanf("%s",s+1); len=strlen(s+1); l=read(); r=read();
for(reg int i=1;i<=len;++i) S2.extend(s[i]-'a',i,1);
for(reg int i=1;i<=len;++i) mx[i]=0; ans=0; now=1; nowlen=0;
for(reg int i=1;i<=len;++i){
while(1){
int nxt=S1.son[now][s[i]-'a'];
if(nxt&&query(rt[nxt],1,slen,l+nowlen,r)){nowlen++,now=nxt; break;}
if(!nowlen) break; --nowlen;
if(nowlen==S1.len[S1.fa[now]]) now=S1.fa[now];
} mx[i]=nowlen;
}
for(reg int i=2;i<=S2.tot;++i) ans+=max(0,S2.len[i]-max(S2.len[S2.fa[i]],mx[S2.id[i]]));
printf("%lld\n",ans);
}
return 0;
}
}
signed main(){return yspm::main();}