后缀数组
后缀数组
原理介绍
排序:
设 \(s\) 为原串, \(s'_{i,j}\) 表示 \(s[i,\min (i+2^j,|s|)]\) ,\(rk_{i,j}\) 表示 \(s'_{i,j}\) 在集合 $ S_j={s'_{i,j}~~|~i\in [1,|s|]}$ 中的排名。
则有 \(rk_{i,j+1}\) 相当于以 \(rk_{i,j}\) 为第一关键字,以 \(rk_{i+2^j,j}\) 为第二关键字排序之后的排名。
排序直接用基数排序即可,时间复杂度 \(O(n\log n)\)。
void RadixSort()//基数排序
{
for(int i=0;i<=m;i++) tak[i]=0;//桶
for(int i=1;i<=n;i++) tak[rk[i]]++;
for(int i=1;i<=m;i++) tak[i]+=tak[i-1];
for(int i=n;i;i--) sa[tak[rk[tp[i]]]--]=tp[i];//从大到小枚举第二关键字赋值
}
void build()
{
for(int i=1;i<=n;i++) rk[i]=s[i],tp[i]=i;
RadixSort();
for(int len=1;len<=n;len*=2)
{
for(int i=1;i<=len;i++) tp[i]=n-len+i;//第二关键字初始化,超出的部分赋成极小值
int p=len;
for(int i=1;i<=n;i++)
if(sa[i]>len) tp[++p]=sa[i]-len;//第二关键字
RadixSort(),std::swap(tp,rk),p=0;//基数排序,保存rk数组
for(int i=1;i<=n;i++)
{
if(tp[sa[i]]!=tp[sa[i-1]]||tp[sa[i]+len]!=tp[sa[i-1]+len]) p++;
rk[sa[i]]=p;//获取排名
}m=p;//重定义值域
}
}
求lcp:
设 \(sa_i\) 为字典序第 \(i\) 小的后缀下标,\(h_i\) 为 \(\texttt{lcp} (s[sa_i,n],s[sa_{i+1},n])\) 。
则一定有 $\texttt{lcp}(s[i,n],s[j,n]) =\min_{p=rk_i}^{rk_{j-1}} h_p $。
证明:不妨设 \(rk_j<rk_i\) , 一定有 \(\texttt{lcp}(s[i,n],s[j,n]) \leq \texttt{lcp}(s[sa_{rk_i-1},n],s[j,n])\),直接归纳证明即可。
求h数组
for(int i=1,j=0;i<=n;i++)
{
if(j) j--;
while(s[i+j]==s[sa[rk[i]-1]+j]) j++;
h[rk[i]]=j;
}
for(int i=1;i<=n;i++) st[i][0]=h[i]; //st表
for(int j=1;j<=19;j++)
for(int i=1;i<=n-(1<<j)+1;i++)
st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
求lcp
int lcp(int l,int r)
{
int x=rk[l],y=rk[r];
if(x>y) swap(x,y); x++;
int k=lg[y-x+1];
return min(st[x][k],st[y-(1<<k)+1][k]);//区间最小值
}
例题[P9482 NOI2023字符串]
求lcp。
二进制分组
原理介绍
如果要求强制在线,那么可以把某个不能支持在线加入但是可以以 \(O(siz)\) 的复杂度合并的某个数据结构多建几个,每次如果存在两个相同大小的这个数据结构,那么将他们两个合并,时间复杂度类似于启发式合并,某个加入的数据结构只会被合并 \(O(\log n)\) 次,对总时间复杂度的贡献就是 \(O(\log n)\),查询的时候对于每个数据结构的答案累加计算即可,这样的合并策略可以发现一共同时也只有 \(O(\log n)\)个数据结构。
代码
void upd(const T &s,int x)
{
rt[++cnt]=&tr[++tot];
insert(s,rt[cnt],x);
siz[cnt]=1;
while(cnt>1&&siz[cnt]==siz[cnt-1])
{
rt[cnt-1]=merge(rt[cnt-1],rt[cnt]);
siz[cnt-1]+=siz[cnt],cnt--;
}
return build(rt[cnt]);
}
例题[[CF710F](Problem - F - Codeforces)]:
AC自动机要求加串删串强制在线,删串直接贡献-1的权值即可。
node* merge(node *x,node *y)
{
if(!x||!y) return x?x:y;
x->ed+=y->ed;
for(int i=0;i<26;i++)
x->son[i]=merge(x->son[i],y->son[i]);
return x;
}
void upd(const string &s,int x)
{
rt[++cnt]=&tr[++tot];
insert(s,rt[cnt],x);
siz[cnt]=1;
while(cnt>1&&siz[cnt]==siz[cnt-1])
{
rt[cnt-1]=merge(rt[cnt-1],rt[cnt]);
siz[cnt-1]+=siz[cnt],cnt--;
}
return build(rt[cnt]);
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
while(n--)
{
int op;string s;
cin>>op>>s;
if(op==1) upd(s,1);
else if(op==2) upd(s,-1);
else {
ll res=0;
for(int i=1;i<=cnt;i++) res+=query(s,rt[i]);
cout<<res<<endl;
}
}
return 0;
}

浙公网安备 33010602011771号