ACM-字符串完全总结(知识点+模板)

目录

  1. 常用STL方法
  2. manacher算法
  3. 字符串Hash
  4. KMP
    4.1普通KMP
    4.2扩展KMP

  5. Trie(字典树)
    5.1 字典树
    5.1 01字典树

  6. 自动机
    6.1 AC自动机
    6.2 AC自动机上的动态规划
    6.3 回文自动机(回文树)

  7. 后缀数组
    7.1后缀数组的常见用法



注意

1.对于字符串问题,最好使用char []来存储,不要用string,否则可能会占用大量内存及减低速度
2.strlen(char []),以及相似方法的复杂度均为O(n),千万不要用在循环里!

一.常用STL方法:

任意进制转换:
    itoa(int n,char* s,int r)  //将10进制n转换为r进制并赋给s
 string:
 流:
      #include<sstream>
      stringstream stream;   //创建名为stream的流
      stream.clear   //重复使用必须清空
      string a,str;
      stream(a);             //将字符串a放入流
      while(a>>str)       //将a流入str中,以空格为拆分进行输出
           cout<< str<< endl;
     迭代器:
       string::iterator it  //创建名为it的迭代器
     反转:
        reverse(s.begin(), s.end());   //原地反转
        s1.assign(s.rbegin(), s.rend());   //反转并赋给s1

 大小写转换:
             transform(s.begin(), s.end(), s.begin(), ::toupper);
             transform(s.begin(), s.end(), s.begin(), ::tolower);

 类型转换:
         string ->int : string s("123");
                        int i = atoi(s.c_str());

         int -> string: int a;
                        stringstream(s) >> a;
 子串:
          string substr(int pos = 0,int n = npos) const;//返回pos开始的n个字符组成的字符串
 插入:s.insert(int p0,string s)    //在p0位置插入字符串s
 更改:
         s.assign(str); //直接
         s.assign(str,1,3);//如果str是”iamangel” 就是把”ama”赋给字符串
         s.assign(str,2,string::npos);//把字符串str从索引值2开始到结尾赋给s
         s.assign(“gaint”); //不说
         s.assign(“nico”,5);//把’n’ ‘I’ ‘c’ ‘o’ ‘\0’赋给字符串
         s.assign(5,’x’);//把五个x赋给字符串
 删除:
      s.erase(13);//从索引13开始往后全删除
      s.erase(7,5);//从索引7开始往后删5个
      iterator erase(iterator it);//删除it指向的字符,返回删除后迭代器的位置  
        iterator erase(iterator first, iterator last);//删除[first,last)之间的所有字符,返回删除后迭代器的位置
 查找:
      int find(char c, int pos = 0) const;//从pos开始查找字符c在当前字符串的位置
      int find(const char *s, int pos = 0) const;//从pos开始查找字符串s在当前串中的位置
      int find(const char *s, int pos, int n) const;//从pos开始查找字符串s中前n个字符在当前串中的位置
      int find(const string &s, int pos = 0) const;//从pos开始查找字符串s在当前串中的位置。
删除所有特定字符:
      str.erase(std::remove(str.begin(), str.end(), 'a'), str.end());

二.manacher算法

能求出以任一点为中点的回文半径(回文串长度),O(n)

#include<iostream>
#include<cstring>
#include <string>
#include<algorithm>
using namespace std;
const int maxl=1100005;
int p[2*maxl+5];   //p[i]-1表示以i为中点的回文串长度
int Manacher(string s)
{
    string now;
    int len=s.size();
    for(int i=0;i<len;i++)      //将原串处理成%a%b%c%形式,保证长度为奇数
    {
       now+='%';
       now+=s[i];
    }
    now+='%';
    int len=now.size();
    int pos=0,R=0;
    for (int i=0;i<len;i++)
    {
        if (i<R) p[i]=min(p[2*pos-i],R-i); else p[i]=1;
        while (0<=i-p[i]&&i+p[i]<len&&now[i-p[i]]==now[i+p[i]]) p[i]++;
        if (i+p[i]>R) {pos=i;R=i+p[i];}
    }
    int MAX=0;
    for (int i=0;i<len;i++)
    {
        cout<<i<<" : "<<p[i]-1<<endl;            //p[i]-1为now串中以i为中点的回文半径,即是s中最长回文串的长度
        cout<<now.substr(i-p[i]+1,2*p[i]-1)<<endl;
        MAX=max(MAX,p[i]-1);
    }
    return MAX;           //最长回文子串长度
}
string a;
int main()
{
    std::ios::sync_with_stdio(false);
    while(cin>>a)
    cout<<Manacher(a)<<endl;
    return 0;
}

三.字符串Hash

hash[i]=(hash[i-1]*seed+str[i])%MOD
将字符串通过hash函数映射成一个数字,并由此判断是否重复等,近似字符串匹配

#include <iostream>
#include <string>
#include <cstring>
#include <unordered_set>
using namespace std;
const int mod=1e6+13;
unordered_set<long long>hash_table;
int eid=0,p[mod];
struct node
{
    long long next;
    string s;
}e[100005];
void insert(int u,string str)
{
    e[eid].s=str;
    e[eid].next=p[u];
    p[u]=eid++;
}
string str;
unsigned int BKDRHash(string str)   //unsigned int 在溢出时会自动取模2^32??
{
    unsigned int seed = 131; // 31 131 1313 13131 131313 etc..
    unsigned int hash = 0;
    for(int i=0;i<str.size();i++)
        hash = (hash * seed + (str[i]))%mod;
    return (hash & 0x7FFFFFFF)%mod;
}
// AP Hash Function
unsigned int APHash(string str)
{
    unsigned int hash = 0;
    int i;
    for (i=0;i<str.size(); i++)
    {
        if ((i & 1) == 0)
            hash ^= ((hash << 7) ^ (str[i]) ^ (hash >> 3));
        else
            hash ^= (~((hash << 11) ^ (str[i]) ^ (hash >> 5)));
    }
    return (hash & 0x7FFFFFFF);
}
int hash_in(string s)
{
    long long u=BKDRHash(s);
    for(int i=p[u];i!=-1;i=e[i].next)    //使用链式前向星模拟链表进行冲突判断
    {
        string v=e[i].s;
        if(v==s) return 0;
    }
    insert(u,s);
    return 1;
}
int main()
{
    memset(p,-1,sizeof(p));
    while(cin>>str)
    {
        cout<<str<<endl;
        cout<<BKDRHash(str)<<endl;
        cout<<APHash(str)<<endl;
        cout<<hash_in(str)<<endl;
    }
    return 0;
}

四.KMP

字符串匹配,并求出匹配位置,O(n+m)

#include <iostream>
#include <string>
using namespace std;
int Next[100005];    //next[i]表示,在t[0,i)中,所有匹配真前缀和真后缀的长度
int n,m;
string s,t;
void GetNext()   //原始版本,原始next[i]表示,在t[0,i)中,所有匹配真前缀和真后缀的长度值右移一位,然后初值赋为-1而得
{
    Next[0] = -1;
    int k = -1;
    int j = 0;
    int len=t.size();
    while (j < len - 1)
    {
        //p[k]表示前缀,p[j]表示后缀
        if (k == -1 || t[j] == t[k])
        {
            ++k;
            ++j;
            Next[j] = k;
        }
        else
        {
            k = Next[k];
        }
    }
}
void getnext()   //优化版,对于重复部分效果更好
{
    int k=Next[0]=-1;  //模式串指针,将next[0]=-1 表示一个哨兵,即是一个通配符,可以和任何字符匹配
    int j=0;     //主串指针
    int len=t.size();
    while(j<len-1)
    {
        if(k==-1 || t[j]==t[k])    //匹配
        {
            k++;
            j++;
            if(t[j]!=t[k])
                Next[j]=k;
            else
                Next[j]=Next[k];
        }
        else          //失配
            k=Next[k];
    }
}
int KMP()     //串全部从0开始
{
    getnext();
    int i=0;
    int j=0;
    int lens=s.size();
    int lent=t.size();
    while(i<lens && j<lent)
    {
        if(j==-1 || s[i]==t[j])
        {
            i++;
            j++;
        }
        else
            j=Next[j];
    }
    if(j==lent)
        return i-j;
    else
        return -1;
}
int main()
{
    while(cin>>s>>t)
        cout<<KMP()<<endl;
    return 0;
}

扩展KMP

求S的所有后缀与T的最长公共前缀,O(n+m)

#include<bits/stdc++.h>
using namespace std;
const int maxn=10086;   //字符串长度最大值
int Next[maxn],exnext[maxn]; //ex数组即为extend数组
string s,t;
//预处理计算Next数组
void getnext(string str)           //next[i]表示,t[i...m]与t[1...m]中的最长公共前缀
{
    int i=0,j,po,len=str.size();
    Next[0]=len;//初始化Next[0]
    while(str[i]==str[i+1]&&i+1<len)//计算Next[1]
        i++;
    Next[1]=i;
    po=1;//初始化po的位置
    for(i=2; i<len; i++)
    {
        if(Next[i-po]+i<Next[po]+po)//第一种情况,可以直接得到Next[i]的值
            Next[i]=Next[i-po];
        else//第二种情况,要继续匹配才能得到Next[i]的值
        {
            j=Next[po]+po-i;
            if(j<0)j=0;//如果i>po+Next[po],则要从头开始匹配
            while(i+j<len&&str[j]==str[j+i])//计算Next[i]
                j++;
            Next[i]=j;
            po=i;//更新po的位置
        }
    }
}
//计算extend数组
void exkmp(string s,string t)                    //exnext[i],表示s[i...n]与t[1...m]中的最长公共前缀
{
    int i=0,j,po,len=s.size(),l2=t.size();
    getnext(t);//计算子串的Next数组
    while(s[i]==t[i] && i<l2&&i<len)//计算ex[0]
        i++;
    exnext[0]=i;
    po=0;//初始化po的位置
    for(i=1; i<len; i++)
    {
        if(Next[i-po]+i<exnext[po]+po)//第一种情况,直接可以得到ex[i]的值
            exnext[i]=Next[i-po];
        else//第二种情况,要继续匹配才能得到ex[i]的值
        {
            j=exnext[po]+po-i;
            if(j<0)j=0;//如果i>ex[po]+po则要从头开始匹配
            while(i+j<len&&j<l2&& s[j+i]==t[j])//计算ex[i]
                j++;
            exnext[i]=j;
            po=i;//更新po的位置
        }
    }
}
int main()
{
    while(cin>>s>>t)
    {
        exkmp(s,t);
        for(int i=0;i<t.size();i++)
            cout<<Next[i]<<endl;
        for(int i=0;i<s.size();i++)
            cout<<exnext[i]<<endl;
    }
    return 0;
}

五.字典树(Trie)

一个节点和其所有孩子都有相同的前缀,即该节点对应的字符串,或者说从根节点出发到该节点的路径,组成的子串
各种操作均为O(n)

常见用法:
1.字符串匹配
2.前缀查询
3.求连续异或和

#include<bits/stdc++.h>
using namespace std;
const int N=100005;
int tot=0;   //tot表示下个结点的编号
int trie[N][26];   //存贮字典树,tire[rt][x],表示其下一个结点的编号,其中rt表示父节点编号,x表示字母
int sum[N];    //保存前缀出现次数
void insert(string ch) {                        //类似链式前向星的思想
    int rt = 0;
    int len=ch.size();
    for (int i = 0; i < len; i++) {
        if (trie[rt][ch[i] - 'a'] == 0)         //当前字母未出现过
            trie[rt][ch[i] - 'a'] = ++tot;
        sum[trie[rt][ch[i]-'a']]++;
        rt = trie[rt][ch[i] - 'a'];
    }
}
bool find(string s)      //查询s是否存在
{
    int rt=0;
    int len=s.size();
    for(int i=0;i<len;i++)
    {
        if(trie[rt][s[i]-'a']==0) return false;     //在rt后没有以s[i]开头的字符串
        rt=trie[rt][s[i]-'a'];    //若有,则继续向下
    }
    return true;
}
int search(string s)        //查询以s为前缀的串个个数
{
    int rt=0;
    int len=s.size();
    for(int i=0;i<len;i++)
    {
        if(trie[rt][s[i]-'a']==0) return 0;
        rt=trie[rt][s[i]-'a'];
    }
    return sum[rt];
}
int main()
{
    string a;
    while(cin>>a)
    {
        insert(a);
        cout<<find(a)<<endl;
        cout<<search(a)<<endl;
    }
    return 0;
}

01字典树

用字典树保存一个数的二进制形式(只由01构成),若想要异或值大,一定要越靠前的数异或后为1,即尽可能使权值较大的数异或后为1
常见用法:
求各种与异或相关的操作

求区间最大异或值

#include<bits/stdc++.h>
#define maxn 1005
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
int a[maxn];
int num[32*maxn][2];
int node[32*maxn][2];
int val[32*maxn];
int sum,ans,l,r,anss,s;
void init(){
    sum=1;
    ans=-inf;
    memset(num,0,sizeof num);
    memset(node,0,sizeof node);
    memset(val,0,sizeof val);
}
void change1(int m,int x){
    int pos=0;
    for(int i=30;i>=0;i--){
        int j=x>>i&1;
        num[pos][j]+=m;
        if(node[pos][j]) pos=node[pos][j];
        else{
            memset(node[sum],0,sizeof node[sum]);
            node[pos][j]=sum++;
            pos=node[pos][j];
        }
    }
    val[pos]=x;
}
int search1(int L,int R,int x){
    int pos=0;
    int w=0;

    for(int i=30;i>=0;i--){
        int j=x>>i&1;
        if(num[pos][!j]){
            w+=1<<i;
            pos=node[pos][!j];
        }
        else    pos=node[pos][j];
    }
    if(w>ans) ans=w,l=L,r=R,anss=val[pos];
}
int main(){
    int t,n;
    cin>>t;
    while(t--){
        init();
        cin>>n;
        for(int i=1;i<=n;i++) cin>>a[i],change1(1,a[i]);
        for(int i=1;i<=n;i++){
            s=0;
            for(int j=i;j<=n;j++){
                s+=a[j];
                change1(-1,a[j]);
                int w=search1(i,j,s);
            }

            for(int j=i;j<=n;j++) change1(1,a[j]);
        }
        cout<<l<<" "<<r<<" "<<anss<<" "<<ans<<endl;
    }
}

六.AC自动机:

结合KMP与trie,相当于在trie上构建了一个next数组,保存每个位置的失配后调整位置
用于多字符串匹配,如在匹配cat时失配,但存在另一个单词cart,则失配指针指向前缀ca,避免重复计算

#include <bits/stdc++.h>
using namespace std;

const int MAXN=1000005;
const int MAXC=26;

struct AC_Automaton {
    int trie[MAXN][MAXC], fail[MAXN], sta[MAXN],Q[MAXN];
    int tot;

    void init() {
        memset(trie, 255, sizeof(trie));
        memset(fail, 0, sizeof(fail));
        tot = 0;
        memset(sta, 0, sizeof(sta));
    }

    void insert(string ch) {              //插入字符串到字典树中
        int rt = 0, l = ch.size();
        for (int i = 0; i < l; i++)
        {
            if (trie[rt][ch[i] - 'a'] == -1) trie[rt][ch[i] - 'a']= ++tot;
            rt = trie[rt][ch[i] - 'a'];
        }
            sta[rt]++;
    }
    void build() {                            //构建失配指针表,相当于next数组
        int l = 0, r = 0;
        for (int i = 0; i < MAXC; i++)
            if (trie[0][i] == -1)
                trie[0][i] = 0;
            else
                Q[++ r] = trie[0][i];
        while (l < r) {
            int rt = Q[++l];
            for (int i = 0; i < MAXC; i++)
                if (trie[rt][i] == -1)
                    trie[rt][i] = trie[fail[rt]][i];
                else {
                    fail[trie[rt][i]] = trie[fail[rt]][i];
                    Q[++ r] = trie[rt][i];
                }
        }
    }
    int solve(string ch) {                         //求字典树中有多少字符串出现在ch中
        int ret = 0, rt = 0, l = ch.size();
        for (int i = 0; i < l; i++) {
            rt = trie[rt][ch[i] - 'a'];
            int tmrt = rt;
            while (tmrt) {
                ret += sta[tmrt];
                sta[tmrt] = 0;
                tmrt = fail[tmrt];
            }
        }
        return ret;
    }
}T;

int main()
{
    std::ios::sync_with_stdio(false);
    int n;
    string a;
    cin>>n;
    T.init();
    while(n--)
    {
        cin>>a;
        T.insert(a);
    }
    T.build();
    string t;
    cin>>t;
    cout<<T.solve(t)<<endl;
    return 0;
}

AC自动机上的动态规划

Trie图上每个点都是一个状态,在AC自动机的状态相互装化,可以实现动态规划

1.求模式串在原串中出现的次数:(所有模式串不同)
构建Trie树时,在每个串结尾节点进行标记,在AC自动机上匹配时,每遇到一次结尾节点即表示成功匹配,ans++

2.求模式串在原串中出现次数,若模式串B是模式串A的子串,则只记录A:
构建Trie树时,在每个串结尾节点进行标记,在AC自动机上匹配时,对经过的子串模式串消除标记,其余与之前相同

3.求原串中不包含任何模式串的串的种类数:
对所有模式串构建AC自动机,模式串的终止的都是非法的,不可经过
dp[i][j]表示长度为i,状态为j的字符串的种类数,枚举所有字符进行状态匹配,答案即为sum(dp[m][i])
若m较小,n较大,可以考虑用矩阵乘法加速dp

4.求一个长度最短的串使得它包含所有模式串:
对所有模式串建立AC自动机,若n很小,可用状态压缩dp
二进制状态j&2^k表示从根节点到该结点的路径上有第k个模式串 dp[i][j]表示状态i,二进制状态j的最短长度,初始化dp[i][j]=0
在Trie上求(0,0)到(i,2^n-1)点的最短路,答案即是dp[i][2^-1]

回文自动机(回文树)

以最长回文为前缀构建的字典树,用回文串代替字典树中的前缀

常用方法:

1.求串S前缀0~i内本质不同回文串的个数(两个串长度不同或者长度相同且至少有一个字符不同便是本质不同)
2.求串S内每一个本质不同回文串出现的次数
3.求串S内回文串的个数(其实就是1和2结合起来)
4.求以下标i结尾的回文串的个数

const int MAXN = 100005 ;  
const int N = 26 ;  

struct Palindromic_Tree {  
    int next[MAXN][N] ;//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成  
    int fail[MAXN] ;//fail指针,失配后跳转到fail指针指向的节点  
    int cnt[MAXN] ; //表示节点i表示的本质不同的串的个数(建树时求出的不是完全的,最后count()函数跑一遍以后才是正确的)
    int num[MAXN] ; //表示以节点i表示的最长回文串的最右端点为回文串结尾的回文串个数
    int len[MAXN] ;//len[i]表示节点i表示的回文串的长度(一个节点表示一个回文串)
    int S[MAXN] ;//存放添加的字符  
    int last ;//指向新添加一个字母后所形成的最长回文串表示的节点。
    int n ;//表示添加的字符个数。
    int p ;//表示添加的节点个数。

    int newnode ( int l ) {//新建节点  
        for ( int i = 0 ; i < N ; ++ i ) next[p][i] = 0 ;  
        cnt[p] = 0 ;  
        num[p] = 0 ;  
        len[p] = l ;  
        return p ++ ;  
    }  

    void init () {//初始化  
        p = 0 ;  
        newnode (  0 ) ;  
        newnode ( -1 ) ;  
        last = 0 ;  
        n = 0 ;  
        S[n] = -1 ;//开头放一个字符集中没有的字符,减少特判  
        fail[0] = 1 ;  
    }  

    int get_fail ( int x ) {//和KMP一样,失配后找一个尽量最长的  
        while ( S[n - len[x] - 1] != S[n] ) x = fail[x] ;  
        return x ;  
    }  

    void add ( int c ) {  
        c -= 'a' ;  
        S[++ n] = c ;  
        int cur = get_fail ( last ) ;//通过上一个回文串找这个回文串的匹配位置  
        if ( !next[cur][c] ) {//如果这个回文串没有出现过,说明出现了一个新的本质不同的回文串  
            int now = newnode ( len[cur] + 2 ) ;//新建节点  
            fail[now] = next[get_fail ( fail[cur] )][c] ;//和AC自动机一样建立fail指针,以便失配后跳转  
            next[cur][c] = now ;  
            num[now] = num[fail[now]] + 1 ;  
        }  
        last = next[cur][c] ;  
        cnt[last] ++ ;  
    }  

    void count () {  
        for ( int i = p - 1 ; i >= 0 ; -- i ) cnt[fail[i]] += cnt[i] ;  
        //父亲累加儿子的cnt,因为如果fail[v]=u,则u一定是v的子回文串!  
    }  
} ;  

七.后缀数组:

计算一个字符串所有后缀经过字典排序后的起始下标结果

sa数组(后缀数组): 将S的n个后缀从小到大进行排序之后把排好序的后缀的开头位置顺次放入SA
rk数组(名次数组): 名次数组Rank[i]保存的是Suffix(i)在所有后缀中从小到大排列的“名次”
height数组:定义height[i]=suffix(sa[i-1])和suffix(sa[i])的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀。那么对于j和k,不妨设rank[j]< rank[k],则有以下性质:
suffix(j)和suffix(k)的最长公共前缀为height[rank[j]+1],height[rank[j]+2],height[rank[j]+3],……,height[rank[k]]中的最小值。

常用方法:

单串问题:

1.可重叠最长重复子串:给定一个字符串,求最长重复子串,这两个子串可重叠
重复子串即是两后缀的公共前缀,最长重复子串,等价于两后缀的最长公共前缀的最大值
只需计算height数组,再比较最大值,就是最长重复子串的长度

2.不可重叠最长重复子串:
直接用height数组不能保证两后缀不重叠,可二分答案,进行判定
问题转化为:原串中是否有长度为k的重复子串,按height数组把后缀分组,每组height值都>=k。
若存在至少一组,SAmax - SAmin >=k,则存在长度为k的重复子串

3.最长k次重复子串:求至少出现k次的最长重复子串,且k个子串可以重叠
先二分答案,将后缀分成如果组,判断有没有一个组的后缀个数不小于k,若有,则存在k个相同的子串满足条件
反之,不存在

4.不同子串的个数:
即对n-sa[i]+1-height[i]求和

双串问题:

1.最长公共子串:给定两个串,求这两个串的最长公共子串
先将两个串拼接在一起,形成A&B的形式,$为分隔符,小于所有字母。再求出新串的后缀数组及height
求排名相邻,原来不在同一个字符串的height值的最大值

2.长度小于k的公共子串个数:求两个串中,长度不小于k的公共子串个数,可以相同
先将两个串拼接,按height值分组后,统计每组中后缀之间的最长公共前缀和
扫描一遍,没遇到一个b的后缀就统计与前面a的后缀能产生多少个长度不小于k的公共子串
a的后缀用一个单调栈维护,再对a的后缀与前面b的后缀也做同样的计算

多串问题:

1.其它串没有的子串:问a串中有多少种字符串集合B中没有的连续子串
先将a串和其它子串拼接,求一遍总的子串个数,再减去a串与集合B相连的子串个数

2.多串的最长公共子串:
将n个字符串拼接,二分答案,将后缀按height值分成若干组,判断是否存在一组后缀属于n个字符串

3.不少于k个串的最长子串
将n个字符串拼接,求后缀数组,二分答案,将后缀按height值分成若干组
判断每组的后缀是否出现在不小于k个的原串中

4.每个串中至少出现2次且不重叠的子串个数:
将所有串拼接,二分答案,按height值分组,若某一组存在每个串至少出现2次,则满足

5.出现或反转后出现在每个字符串的最长子串:
先将每个字符串反转,在将反转后的与原来的一起全部拼接,求后缀数组。
二分答案,将后缀分组,判断时看是否有一组后缀在每个原来的串或反转后的串中出现

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;

const int BufferSize = 1180000;

char buffer[BufferSize];
char *out_tail = buffer;

const int N = 100005;

int n;
char s[N];

namespace SA {
int sa[N], rk[N], ht[N], s[N<<1], t[N<<1], p[N], cnt[N], cur[N];
#define pushS(x) sa[cur[s[x]]--] = x
#define pushL(x) sa[cur[s[x]]++] = x
#define inducedSort(v) fill_n(sa, n, -1); fill_n(cnt, m, 0);                  \
    for (int i = 0; i < n; i++) cnt[s[i]]++;                                  \
    for (int i = 1; i < m; i++) cnt[i] += cnt[i-1];                           \
    for (int i = 0; i < m; i++) cur[i] = cnt[i]-1;                            \
    for (int i = n1-1; ~i; i--) pushS(v[i]);                                  \
    for (int i = 1; i < m; i++) cur[i] = cnt[i-1];                            \
    for (int i = 0; i < n; i++) if (sa[i] > 0 &&  t[sa[i]-1]) pushL(sa[i]-1); \
    for (int i = 0; i < m; i++) cur[i] = cnt[i]-1;                            \
    for (int i = n-1;  ~i; i--) if (sa[i] > 0 && !t[sa[i]-1]) pushS(sa[i]-1)
void sais(int n, int m, int *s, int *t, int *p) {
    int n1 = t[n-1] = 0, ch = rk[0] = -1, *s1 = s+n;
    for (int i = n-2; ~i; i--) t[i] = s[i] == s[i+1] ? t[i+1] : s[i] > s[i+1];
    for (int i = 1; i < n; i++) rk[i] = t[i-1] && !t[i] ? (p[n1] = i, n1++) : -1;
    inducedSort(p);
    for (int i = 0, x, y; i < n; i++) if (~(x = rk[sa[i]])) {
        if (ch < 1 || p[x+1] - p[x] != p[y+1] - p[y]) ch++;
        else for (int j = p[x], k = p[y]; j <= p[x+1]; j++, k++)
            if ((s[j]<<1|t[j]) != (s[k]<<1|t[k])) {ch++; break;}
        s1[y = x] = ch;
    }
    if (ch+1 < n1) sais(n1, ch+1, s1, t+n, p+n1);
    else for (int i = 0; i < n1; i++) sa[s1[i]] = i;
    for (int i = 0; i < n1; i++) s1[i] = p[sa[i]];
    inducedSort(s1);
}
template<typename T>
int mapCharToInt(int n, const T *str) {
    int m = *max_element(str, str+n);
    fill_n(rk, m+1, 0);
    for (int i = 0; i < n; i++) rk[str[i]] = 1;
    for (int i = 0; i < m; i++) rk[i+1] += rk[i];
    for (int i = 0; i < n; i++) s[i] = rk[str[i]] - 1;
    return rk[m];
}
// Ensure that str[n] is the unique lexicographically smallest character in str.
template<typename T>
void suffixArray(int n, const T *str) {
    int m = mapCharToInt(++n, str);
    sais(n, m, s, t, p);
    for (int i = 0; i < n; i++) rk[sa[i]] = i;
    for (int i = 0, h = ht[0] = 0; i < n-1; i++) {
        int j = sa[rk[i]-1];
        while (i+h < n && j+h < n && s[i+h] == s[j+h]) h++;
        if (ht[rk[i]] = h) h--;
    }
}
};

int main()
{
    scanf("%s", s);
    n = strlen(s);
    s[n] = 'a' - 1;

    SA::suffixArray(n, s);

    for (int i = 1; i <= n; ++i)
        cout<<SA::sa[i]+1<<" ";
    cout<<endl;

    *out_tail++ = '\n';
    for (int i = 2; i <= n; ++i)
        cout<<SA::ht[i]<<" ";
    cout<<endl;
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43093481/article/details/82318377
今日推荐