字符串基础

搬一下

upd on 25.7.10:增加了 SAM,对原来的部分内容进行了重构,增加了一些理解。

再不写就要忘完了。

给自己看的。

一些前言:

OI中的字符串算法实际上只有两种,第一种的原理是通过我们原本处理的序列信息,得出新的信息。第二种是建立自动机,分析自动机的性质得出结论,总之,就是通过已知的信息,得到新的东西。

hash

这个还是会的。

void init(){
    p[0]=1;rep(i,1,n){p[i]=p[i-1]*P;hs[i]=hs[i-1]*P+s[i]-'a';}
}
ull gv(int l,int r){return hs[r]-hs[l-1]*p[r-l+1];}

KMP

比较基础,现在应该彻底弄懂了。

我们需要求:字符串 \(S\) 前缀的最长 Border,就是 next 数组。

假设我们现在在最后一个蓝点 \(i\),我们原来的指针在红点 \(j\),我们看一下 \(S_{j+1}\) 等不等于 \(S_{i}\),等于的话往后直接 j++,否则继续往前面跳,跳指的是 \(j=next_j\)

这个东西虽然很基础,但是这给我们带来一些思考,字符串的很多优化都是基于前面已经算出的东西,进行下一步操作

Trie

简单,但是有一些经典Trick蛤。

Manacher 和 Exkmp

这两个东西放在一起,是因为他们本质实际相同。

参考了魏老师的字符串blog。

Manacher:求一个字符串的最长回文串。

首先为了避免讨论,将它中间插入字符,例如\(S=\mathtt{aabbccdd}\),变为\(S=\mathtt{a @ a @b @b @c @ c @ d@ d}\)

考虑枚举回文串中间的点,我们直接向两边扩散,这个是 \(O(n^2)\) 的,不能通过。

实际上我们在之前已经算过了很多内容,由于回文的对称性很多都是算过的,我们记录我们目前最远算到的位置,如果小于,根据对称性,我们可以画一下图

所以第 \(i\) 处的答案就可能是 \(2 \times pos -i\),然后与有边界取一个 min

int manacher(){
    string res="|&";
    rep(i,0,str.size()-1){res+=str[i];res+='&';}
    int pos=0,ans=0,mir=0;
    rep(i,1,res.size()-1){
        p[i]=(i<mir)?(min(p[2*pos-i],mir-i)):1;
        while(res[i-p[i]]==res[i+p[i]]) p[i]++;
        qma(ans,p[i]-1);
        if(i+p[i]>mir)  mir=i+p[i],pos=i;
    }
    return ans;
}

Exkmp:主要是字符串与字符串的每一个后者的最长公共前缀。

类似马拉车,我们可以即当前前缀匹配的区间为\([L,R]\),紧接着我们可以暴力向后匹配。

z[1]=m;
for(int i=2,l=0,r=0;i<=m;i++){
    z[i]=(i>r)?0:(min(r-i+1,z[i-l+1]));
    while(T[1+z[i]]==T[i+z[i]]) z[i]++;
    if(i+z[i]-1>r)    l=i,r=i+z[i]-1;
}
for(int i=1,l=0,r=0;i<=n;i++){
    p[i]=(i>r)?0:(min(r-i+1,z[i-l+1]));
    while(p[i]<m&&T[1+p[i]]==S[i+p[i]]) p[i]++;
    if(i+p[i]-1>r)    l=i,r=i+p[i]-1;
}

AC自动机

什么是自动机:Eg:Trie树。

\(\mathtt{S:用来建立自动机},\mathtt{T:用来喂给自动机}\)

我们尝试去构造啊,先把所有的东西放到一个 Tire 树上,类似与 KMP,我们尝试去失配啊,我们考虑一个 Tire 树的结构,如果我们向下 bfs 的时候,有一个是找到了,我们直接就跳到 \(fail_u\) 下面就行了,否则,我们直接让他联系到 \(fail_u\)

Fail Tree:有一个经典结论,把所有 S 建立起来,然后我们一直跳 T (在 fail 树上),给它做一个+1,最后在查询最后一个点的子树和,就是出现次数。

namespace ACAM{
    int tr[N][26],tot=0,fail[N*26];
    void insert(string str,int id){
        int u=0;
        rep(i,0,str.size()-1){
            int now=str[i]-'a';
            if(!tr[u][now]) tr[u][now]=++tot;
            u=tr[u][now];
        }
    }
    void getfail(){
        queue<int>q;
        rep(i,0,25){if(tr[0][i])  q.push(tr[0][i]);}
        while(!q.empty()){
            int u=q.front();q.pop();
            rep(i,0,25){
                if(tr[u][i]){
                    fail[tr[u][i]]=tr[fail[u]][i];
                    q.push(tr[u][i]);
                }else   tr[u][i]=tr[fail[u]][i];
            }
        }
    }
}

SA

不嘻嘻

咕咕。

void qsort(){
    rep(i,0,M)  tax[i]=0;
    rep(i,1,n)  tax[rnk[i]]++;
    rep(i,1,M)  tax[i]+=tax[i-1];
    atrep(i,1,n)    sa[tax[rnk[tp[i]]]--]=tp[i];
}
void SA(){
    M=128*128;
    rep(i,1,n)  rnk[i]=(int)s[i],tp[i]=i;//初始化
    qsort();
    for(int w=1,p=0;w<=n;M=p,w<<=1){// w /to 2w
        p=0;
        //第二关键字排序
        rep(i,1,w)  tp[++p]=n-w+i;//从n-w+1 一直到 n
        rep(i,1,n)  if(sa[i]>w) tp[++p]=sa[i]-w;
        qsort();//第一关键字排序
        swap(tp,rnk);//剩下的交换,没用
        rnk[sa[1]]=p=1;
        rep(i,2,n){
            rnk[sa[i]]=(tp[sa[i-1]+w]==tp[sa[i]+w]&&tp[sa[i-1]]==tp[sa[i]])?p:++p;
        }//去重
    }
    rep(i,1,n)  cout<<sa[i]<<" ";
}

SAM

接下来的东西我觉得多头不会SAM认识非常深刻。但是太抽象了,写一点自己的理解。

我们需要让它干什么:维护后缀信息。

怎们做:Trie 树,把所有后缀插入进去,复杂度是 \(O(n^2)\)

我们如果能把自动机(前面说过,Trie 树也是自动机),让他的边和点数量尽量的少,我们就达到任务了。

Endpos:对于一个字串 \(p\),它在原串出现的右端点的集合。后文我们记为 \(\mathrm{endpos} (p)\)

Lemma 1:对于两个子串 \(\mathrm{p,q},\mathrm{endpos} (p)=\mathrm{endpos} (q)\)\(\mathrm{|p|}<\mathrm{|q|}\)\(\mathrm{p}\)\(\mathrm{q}\) 的后缀。

Lemma 2.1:对于两个子串 \(\mathrm{p,q}\),要么 \(\mathrm{endpos} (p) \in \mathrm{endpos} (q)\),要么\(\mathrm{endpos} (p) \varnothing {endpos} (q)\)

Lemma 2.2:在 Lemma 2.2 的基础上,同一个,我们将 endpos 完全相同的字串,放入一个集合,我们叫这个东西 endpos 等价类,同一个等价类中,字符串的长度大小是连续的。

你能够发现,我们能用 endpos 这个东西去描述子串的结构。这个就是多头 PPT 上的内容的意思。

Lemma 3:endpos等价类个数的级别是 \(O(n)\)

Proof:没必要。

接下来我们根据引理3,我们将 endpos 等价类这个东西,建立一棵树(实际上也是自动机)例如\(\mathrm{S}=\mathtt{aababa}\)的 parent 树如下图(这个东西我觉得我不好描述):

我们现在可以开始构造自动机了,根据前面说的,我们实际上是维护 endpos 等价类产生的 parent 树,并且让他成为一个自动机的形状。

我们自动机的构造是在线的。所以我们不妨考虑将 \(\mathrm{S_i}\) 这一位加进去会发生什么。

先咕咕了。

构造的事情不重要,记住一个东西,自动机从上到下,parent 树从下到上。

posted @ 2025-06-30 21:24  q1uple  阅读(11)  评论(1)    收藏  举报
1