String

String

最长公共子串

$dp[i][j]$表示前 $i$ 个 $a$ 和前 $j$ 个 $b$ 组成的最长公共子串长度

最长公共子序列

$dp[i][j]$表示的前 $i$ 个 $a$ 和前 $j$ 个 $b$ 组成最长公共子序列长度

最长上升公共子序列

$dp[i][j]$ 表示前 $i$ 个 $a$ 和前 $j$ 个 $b$ 组成的以 $b[j]$ 结尾的最长上升子公共子序列长度

最长回文子序列

最长回文子串

  • Manacher 算法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
char s[N];
int Manacher(char *s, int len) {
char Ma[2 * N + 10];
int l = 0, ans = 0, Mp[2 * N + 10];
Ma[l++] = '$';
Ma[l++] = '#';
for (int i = 0; i < len; i++) {
Ma[l++] = s[i];
Ma[l++] = '#';
}
Ma[l] = 0;
int mx = 0, id = 0;
for (int i = 1; i < l; i++) {
Mp[i] = mx > i ? min(Mp[2 * id - i], mx - i) : 1;
while (Ma[i + Mp[i]] == Ma[i - Mp[i]]) Mp[i]++;
if (i + Mp[i] > mx) {
mx = i + Mp[i];
id = i;
}
}
for0(i, 2 * len + 2) ans = max(ans, Mp[i] - 1);
return ans;
}

int Manacher(char *s, int len) {//s 从1开始
int ans = 0, r[N];
int R = 0, id = 0;
for (int i = 1; i <=len; i++) {
r[i] = R>i ? min(r[2 * id - i], R - i):0; //r[i] 表示以 i 为中心的回文串半径(不包括 i )
while (i+r[i]+1<=len&&s[i - r[i] - 1]==s[i + r[i] + 1] ) r[i]++;
if (i + r[i] > R) {
R = i + r[i];
id = i;
}
ans=max(ans,2*r[i]+1);
}
id=R=0;
for(int i=2;i<=len;i++){
r[i] = R>i ? min(r[2 * id - i], R - i + 1):0;//r[i] 表示以 i-1 和 i 为中心的回文串半径
while (i+r[i]<=len&&s[i - r[i] - 1]==s[i + r[i]]){
r[i]++;
}
if (i + r[i]-1 > R) {
R = i + r[i]-1;
id = i;
}
ans=max(ans,2*r[i]);
}
return ans;
}

回文自动机

功能:

  • 求串 S 前缀 0~i 内本质不同回文串的个数(两个串长度不同或者长度相同且至少有一个字符不同便是本质不同)
  • 求串 S 内每一个本质不同回文串出现的次数
  • 求串 S 内回文串的个数(其实就是1和2结合起来)
  • 求以下标 i 结尾的回文串的个数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
const int C = 26;
struct Palindromic_Tree {
int next[N][C] ;//next指针,next指针和字典树类似,指向的串为当前串两端加上同一个字符构成
int fail[N] ;//fail指针,失配后跳转到fail指针指向的节点
int cnt[N] ;//cnt[i]表示节点i表示的本质不同的串的个数(建树时求出的不是完全的,最后count()函数跑一遍以后才是正确的)
int num[N] ;//num[i]表示以节点i表示的最长回文串的最右端点为回文串结尾的回文串个数。
int len[N] ;//len[i]表示节点i表示的回文串的长度
int S[N] ;//存放添加的字符
int last ;//指向上一个字符所在的节点,方便下一次add
int n ;//字符数组指针
int p ;//节点指针

int newnode ( int l ) {//新建节点
for ( int i = 0 ; i < C ; ++ 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的子回文串!
}
} PAM;

字符串哈希

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
ull P = 1313131;
ull sqr[N/2],has[N/2],V[N];
int Laxt[N],Next[N],cnt=0;
const int MOD = 2e6+7;

bool _insert(ull Now){
int u=Now%MOD;
for(int i=Laxt[u];i;i=Next[i]){
if(V[i]==Now) return 0;
}
Next[++cnt]=Laxt[u];
Laxt[u]=cnt;
V[cnt]=Now;
return 1;
}
int ans=0;
void _hash(int x,int y){
ull Now=has[y]-has[x-1]*sqr[y-x+1];//求s[x]···s[y]的hash值
if(_insert(Now)) ++ans;
}
void init(char *s,int Len){//s 从1开始
sqr[0]=1;
for(int i=1;i<=Len;i++){
sqr[i]=sqr[i-1]*P;
has[i]=has[i-1]*P+s[i];
}
}

序列自动机

$nxt[i][j]$ 表示在原串 $s$ (从 $1$ 开始) 第 $i$ 位后面的第一个 $j$ 出现的位置,设串长为 $n$ ,字符集大小为 $a$,预处理时间复杂度为 $O(n*a)$

1
2
3
4
for(int i=n;i;i--){
for(int j=0;j<26;j++) nxt[i-1][j]=nxt[i][j];
nxt[i-1][s[i]-'a']=i;
}

求子序列个数

1
2
3
4
5
6
7
int dfs(int x){
if(f[x]) return f[x];
for(r int i=1;i<=a;i++)
if(next[x][i]) f[x]+=dfs(next[x][i]);
return ++f[x];
}
//调用:dfs(0);

求回文子序列或两个串的公共子序列个数等

1
2
3
4
5
6
7
int dfs(int x,int y){//表示目前字符是串1的第x位,串2的第y位
if(f[x][y]) return f[x][y];
for(r int i=1;i<=a;i++)
if(next1[x][i]&&next2[y][i]) f[x][y]+=dfs(next1[x][i],next2[y][i]);
return ++f[x][y];
}
//调用:dfs(0,0);

求一个串的回文子序列个数

对原串和反串构造 $next$ 数组, 然后 $dfs$ , 对回文分奇偶, $dfs$ 过程中应该保证 $x+y<=n+1$ , 若 $x+y=n+1$ 为奇串, 否则为偶串. $dfs $ 到一个偶串时,可能会有奇串被漏掉(如自动机只能找到 $abba$,却忽略了 $aba$) 因此,我们考虑手动加上被忽略的奇串,同时注意已经找到的奇串不能再加一次(如 $ababa$),只要特判一下就可以了,代码如下。

1
2
3
4
5
6
7
8
9
10
11
int dfs(int x,int y){
if(f[x][y]) return f[x][y];//记忆化
for(int i=1;i<=26;i++)
if(next1[x][i]&&next2[y][i]){
if(next1[x][i]+next2[y][i]>n+1) continue;//x+y>n+1,状态不合法,不进行统计
if(next1[x][i]+next2[y][i]<n+1) f[x][y]++;
//满足x+y=n+1的奇串不会被漏掉,其他奇串需要特别统计
f[x][y]=(f[x][y]+dfs(next1[x][i],next2[y][i]))%mod;
}
return ++f[x][y];
}

给定三个串 $A,B.C$, 求一个最长的 $A,B$ 的公共子序列 $S$, 要求 $C$ 是 $S$ 的子序列

再加一个参数 $z$ 表示当前字符是 $C$ 的第 $z$ 位, 但这么做显然是错的,因为匹配过程中 $C$ 的某些字符可能会被跳过, 所以需要对 $next$ 做一些修改

1
2
3
4
5
for(int i=1;i<=26;i++) nextc[n][i]=n;//字符集为26个字母,C长度为n
for(int i=0;i<n;i++){
for(r int j=1;j<=26;j++) nextc[i][j]=i;
nextc[i][c[i+1]]=i+1;
}

KMP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int Next[N];// Next[i] 为满足 p[i-z,...,i-1]=p[0,...,z-1] 的最大 z 值(就是 p 的自身匹配)
void kmp_pre(char *p, int p_len,int Next[]) {
int i = 0, j = Next[0] = -1;
while (i < p_len) {
while (j != -1 && p[i] != p[j]) j = Next[j];
Next[++i] = ++j;
}
}
int KMP(char *p, char *s) { // p 是模式串,s 是主串
int ans = 0, i = 0, j = 0, p_len = strlen(p), s_len = strlen(s);
kmp_pre(p, p_len, Next);
while (i < s_len) {
while (-1 != j && s[i] != p[j]) j = Next[j];
i++;
j++;
if (j >= p_len) {
ans++;
j = Next[j];
}
}
return ans;
}

扩展KMP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
int Extend[N], Next[N];
void GetNext(string S) { // Next[i] 为满足 S[i,...,i+z-1]=S[0,...,z-1] 的最大 z 值,即S[i]...S[s_len-1]与S的最长公共前缀长度
int s_len = (int)S.size(), a = 0, p = 0;
Next[0] = s_len;
for (int i = 1, j = -1; i < s_len; i++, j--) {
if (j < 0 || i + Next[i - a] >= p) {
if (j < 0) {
p = i;
j = 0;
}
while (p < s_len && S[p] == S[j]) {
p++;
j++;
}
Next[i] = j;
a = i;
} else Next[i] = Next[i - a];
}
}
void exKMP(string P, string S) { // P[i]...P[p_len-1]与S的最长公共前缀的长度
GetNext(S);
int a = 0, p = 0, p_len = (int)P.size(), s_len = (int)S.size(); //记录匹配成功的字符的最远位置p,及起始位置a
for (int i = 0, j = -1; i < p_len; i++,j--) { // j即等于p与i的距离,其作用是判断i是否大于p(如果j<0,则i大于p)
if (j < 0 || i + Next[i - a] >= p) {
if (j < 0) {
p = i;
j = 0;
}
while (p < p_len && j < s_len && P[p] == S[j]) {
p++;
j++;
}
Extend[i] = j;
a = i;
} else Extend[i] = Next[i - a];
}
}

字典树

  • 数组实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    int ch[N][30], tot, val[N];
    void Insert(char *s) {
    int root = 0, len = (int)strlen(s);
    for0(i, len) {
    int c = s[i] - 'a';
    if (!ch[root][c]) {
    mem0(ch[tot + 1]); //使用的时候才用memset开辟空间可以减低空间复杂度
    ch[root][c] = ++tot;
    }
    root = ch[root][c];
    val[root]++;
    }
    }
    int Query(char *s) {
    int root = 0;
    int len = (int)strlen(s);
    for0(i, len) {
    int c = s[i] - 'a';
    if (!ch[root][c]) return 0;
    root = ch[root][c];
    }
    return val[root];
    }
  • 指针实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    const int MAX = 26;
    struct Trie {
    Trie *Next[MAX];
    int count;
    Trie() {
    count = 1;
    memset(Next, NULL, sizeof(Next));
    }
    } * root;
    void Insert(string s) {
    Trie *tmpRoot = root, *tmp;
    for0(i, s.size()) {
    if (tmpRoot->Next[s[i] - 'a'] == NULL) {
    tmp = new Trie;
    tmpRoot->Next[s[i] - 'a'] = tmp;
    tmpRoot = tmp;
    } else {
    tmpRoot = tmpRoot->Next[s[i] - 'a'];
    tmpRoot->count++;
    }
    }
    }
    int Query(string s) {
    Trie *tmpRoot = root;
    for0(i, s.size()) {
    if (tmpRoot->Next[s[i] - 'a'] == NULL) return 0;
    tmpRoot = tmpRoot->Next[s[i] - 'a'];
    }
    return tmpRoot->count;
    }
    void Free(Trie *T) {
    if (T == NULL) return;
    for (int i = 0; i < MAX; ++i) {
    if (T->Next[i]) Free(T->Next[i]);
    }
    delete (T);
    }

AC自动机

多模匹配算法,多个模式串,在文本串中找出拥有的模式串数,求 $fail$ : 直接与根节点相连的节点来说,如果这些节点失配,他们的Fail指针直接指向root即可;假设当前节点为father,其孩子节点记为child。求child的Fail
指针时,首先我们要找到其father的Fail指针所指向的节点,假如是t的话,我们就要看t的孩子中有没有和child节点所表示的字母相同的节点,如果有的话,
这个节点就是child的fail指针,如果发现没有,则需要找father->fail->fail这个节点,然后重复上面过程,如果一直找都找不到,则child的Fail指针就要指向root。

  • 数组实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    struct Trie {
    int next[N][26], fail[N], end[N], root, L; // N的范围为所有pattern的字符数
    int newnode() {
    for (int i = 0; i < 26; i++) next[L][i] = -1;
    end[L++] = 0;
    return L - 1;
    }
    void init() {
    L = 0;
    root = newnode();
    }
    void insert(char buf[]) {
    int len = (int)strlen(buf), now = root;
    for (int i = 0; i < len; i++) {
    if (next[now][buf[i] - 'a'] == -1)next[now][buf[i] - 'a'] = newnode();
    now = next[now][buf[i] - 'a'];
    }
    end[now]++;
    }
    void build() {
    queue<int> Q;
    fail[root] = root;
    for (int i = 0; i < 26; i++) {
    if (next[root][i] == -1)next[root][i] = root;
    else {
    fail[next[root][i]] = root;
    Q.push(next[root][i]);
    }
    }
    while (!Q.empty()) {
    int now = Q.front();
    Q.pop();
    for (int i = 0; i < 26; i++) {
    if (next[now][i] == -1)next[now][i] = next[fail[now]][i];
    else {
    fail[next[now][i]] = next[fail[now]][i];
    Q.push(next[now][i]);
    }
    }
    }
    }
    int query(char buf[]) {
    int len = (int)strlen(buf), now = root, res = 0;
    for (int i = 0; i < len; i++) {
    now = next[now][buf[i] - 'a'];
    int temp = now;
    while (temp != root) {
    res += end[temp];
    end[temp] = 0;
    temp = fail[temp];
    }
    }
    return res;
    }
    };
    char *pattern, *str;
  • 指针实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    const int MAX = 26;
    int tot;
    vii failTree[N];
    struct Node {
    int id;
    Node *Next[MAX];
    Node *Fail; //失配指针,类似next数组,最大的next是和自身比较,fail是和其它匹配串比较
    Node() {
    memset(Next, 0, sizeof(Next));
    Fail = 0;
    id=tot++;
    }
    } * root;
    int Insert(char s[]) {
    Node *p = root;
    int len=strlen(s);
    for(int i=0;i< len;i++) {
    int c = s[i] - 'a';
    if (p->Next[c] == NULL) {
    Node *newnode = new Node;
    p->Next[c] = newnode;
    p = newnode;
    } else p = p->Next[c];
    }
    return p->id;
    }
    void BuildFail() {
    queue<Node *> que;
    root->Fail=root;
    for(int i=0;i<26;i++){
    if(root->Next[i]){
    root->Next[i]->Fail=root;
    que.push(root->Next[i]);
    }else root->Next[i]=root;
    }
    while (!que.empty()) {
    Node *p = que.front();
    que.pop();
    failTree[p->Fail->id].pu_b(p->id);
    for(int i=0;i<26;i++){
    if(p->Next[i]){
    p->Next[i]->Fail=p->Fail->Next[i];
    que.push(p->Next[i]);
    }else p->Next[i]=p->Fail->Next[i];
    }
    }
    }

后缀数组

倍增算法 $O(n*logn)$

  • 待排序数组长度为n, 放在[0,n-1]中,在最后面补一个0,需要保证每个元素大于零
  • srank[0,n-1]为有效值,srank[n]必定为0无效值,srank[i]保存Suffix(i)在所有后缀中从小到大排列的“名次”。
  • sa[1,n]为有效值,sa[0] 必定为n是无效值, sa[i]表示排名第i的起点为sa[i];
  • height[2,n]为有效值,suffix(sa[i-1])和suffix(sa[i])的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    int t1[N],t2[N],c[N],srank[N],height[N],sa[N];
    bool cmp(int *r,int a,int b,int l){
    return r[a] == r[b] && r[a+l] == r[b+l];
    }
    void DA(int str[],int n,int m){//m为字符串中的最大值+1
    n++;
    int i, j, p, *x = t1, *y = t2;
    //第一轮基数排序,如果s 的最大值很大,可改为快速排序
    for(i = 0;i < m;i++)c[i] = 0;
    for(i = 0;i < n;i++)c[x[i] = str[i]]++;
    for(i = 1;i < m;i++)c[i] += c[i-1];
    for(i = n-1;i >= 0;i--)sa[--c[x[i]]] = i;
    for(j = 1;j <= n; j <<= 1){
    p = 0;
    //直接利用sa 数组排序第二关键字
    for(i = n-j; i < n; i++)y[p++] = i;//后面的j 个数第二关键字为空的最小
    for(i = 0; i < n; i++)if(sa[i] >= j)y[p++] = sa[i] - j;//这样数组y保存的就是按照第二关键字排序的结果
    //基数排序第一关键字
    for(i = 0; i < m; i++)c[i] = 0;
    for(i = 0; i < n; i++)c[x[y[i]]]++;
    for(i = 1; i < m;i++)c[i] += c[i-1];
    for(i = n-1; i >= 0;i--)sa[--c[x[y[i]]]] = y[i];//根据sa 和x 数组计算新的x 数组
    swap(x,y);
    p = 1; x[sa[0]] = 0;
    for(i = 1;i < n;i++)x[sa[i]] = cmp(y,sa[i-1],sa[i],j)?p-1:p++;
    if(p >= n)break;
    m = p;//下次基数排序的最大值
    }
    int k = 0;
    n--;
    for(i = 0;i <= n;i++)srank[sa[i]] = i;
    for(i = 0;i < n;i++){
    if(k)k--;
    j = sa[srank[i]-1];
    while(str[i+k] == str[j+k])k++;
    height[srank[i]] = k;
    }
    }
    int r[N];

DC3算法 $O(n)$

  • 所有的相关数组都要开三倍
  • 待排序数组长度为n, 放在[0,n-1]中,在最后面补一个0
  • srank[0,n-1]为有效值,srank[n]必定为0无效值,srank[i]保存Suffix(i)在所有后缀中从小到大排列的“名次”
  • sa[1,n]为有效值,sa[0] 必定为n是无效值, sa[i]表示排名第i的起点为sa[i];
  • height[2,n]为有效值,suffix(sa[i-1])和suffix(sa[i])的最长公共前缀,也就是排名相邻的两个后缀的最长公共前缀

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    #define F(x) ((x)/3+((x)%3==1?0:tb))
    #define G(x) ((x)<tb?(x)*3+1:((x)-tb)*3+2)
    int wa[N],wb[N],wv[N],wss[N],sa[N],srank[N],height[N];
    int c0(int *r,int a,int b){
    return r[a] == r[b] && r[a+1] == r[b+1] && r[a+2] == r[b+2];
    }
    int c12(int k,int *r,int a,int b){
    if(k == 2) return r[a] < r[b] || ( r[a] == r[b] && c12(1,r,a+1,b+1) );
    else return r[a] < r[b] || ( r[a] == r[b] && wv[a+1] < wv[b+1]);
    }
    void sort(int *r,int *a,int *b,int n,int m){
    int i;
    for(i = 0;i < n;i++)wv[i] = r[a[i]];
    for(i = 0;i < m;i++)wss[i] = 0;
    for(i = 0;i < n;i++)wss[wv[i]]++;
    for(i = 1;i < m;i++)wss[i]+=wss[i-1];
    for(i = n-1;i >= 0;i--)b[--wss[wv[i]]] = a[i];
    }
    void dc3(int *r,int *sa,int n,int m){
    int i, j, *rn = r + n;
    int *san = sa + n, ta = 0, tb = (n+1)/3, tbc = 0, p;
    r[n] = r[n+1] = 0;
    for(i = 0;i < n;i++)if(i %3 != 0)wa[tbc++] = i;
    sort(r + 2, wa, wb, tbc, m);
    sort(r + 1, wb, wa, tbc, m);
    sort(r, wa, wb, tbc, m);
    for(p = 1, rn[F(wb[0])] = 0, i = 1;i < tbc;i++)rn[F(wb[i])] = c0(r, wb[i-1], wb[i]) ? p - 1 : p++;
    if(p < tbc)dc3(rn,san,tbc,p);
    else for(i = 0;i < tbc;i++)san[rn[i]] = i;
    for(i = 0;i < tbc;i++) if(san[i] < tb)wb[ta++] = san[i] * 3;
    if(n % 3 == 1)wb[ta++] = n - 1;
    sort(r, wb, wa, ta, m);
    for(i = 0;i < tbc;i++)wv[wb[i] = G(san[i])] = i;
    for(i = 0, j = 0, p = 0;i < ta && j < tbc;p++)sa[p] = c12(wb[j] % 3, r, wa[i], wb[j]) ? wa[i++] : wb[j++];
    for(;i < ta;p++)sa[p] = wa[i++];
    for(;j < tbc;p++)sa[p] = wb[j++];
    }
    void DA(int str[],int n,int m){
    for(int i = n;i < n*3;i++)str[i] = 0;
    dc3(str, sa, n+1, m);
    int i,j,k = 0;
    for(i = 0;i <= n;i++)srank[sa[i]] = i;
    for(i = 0;i < n; i++){
    if(k) k--;
    j = sa[srank[i]-1];
    while(str[i+k] == str[j+k]) k++;
    height[srank[i]] = k;
    }
    }
    string s;
    int r[N];

应用

  • 本质不同子串

后缀自动机

模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
const int CHAR = 26;
struct SAM_Node {
SAM_Node *fa, *next[CHAR];
int len;
long long cnt;
void clear() {
fa = 0;
memset(next, 0, sizeof(next));
cnt = 0;
}
} pool[N * 2];
SAM_Node *root, *tail;
SAM_Node* newnode(int len) {
SAM_Node* cur = tail++;
cur->clear();
cur->len = len;
return cur;
}
void SAM_init() {
tail = pool;
root = newnode(0);
}
SAM_Node* extend(SAM_Node* last, int x) {
SAM_Node *p = last, *np = newnode(p->len + 1);
while (p && !p->next[x]) p->next[x] = np, p = p->fa;
if (!p)
np->fa = root;
else {
SAM_Node* q = p->next[x];
if (q->len == p->len + 1)
np->fa = q;
else {
SAM_Node* nq = newnode(p->len + 1);
memcpy(nq->next, q->next, sizeof(q->next));
nq->fa = q->fa;
q->fa = np->fa = nq;
while (p && p->next[x] == q) p->next[x] = nq, p = p->fa;
}
}
return np;
}

//2
struct NODE{
int ch[26];
int len,fa;
void newNode(int _len){memset(ch,0,sizeof(ch));len=_len;}
}pool[N<<1];
int las=1,tot=1;
void init(){
las=tot=1;
memset(pool[1].ch,0,sizeof(pool[1].ch));
pool[1].len=0;
}
void add(int c){
int p=las,np=las=++tot;
pool[np].newNode(pool[p].len+1);
for(;p&&!pool[p].ch[c];p=pool[p].fa)pool[p].ch[c]=np;
if(!p)pool[np].fa=1;//以上为case 1
else{
int q=pool[p].ch[c];
if(pool[q].len==pool[p].len+1)pool[np].fa=q;//以上为case 2
else{
int nq=++tot;
pool[nq]=pool[q];
pool[nq].len=pool[p].len+1;
pool[q].fa=pool[np].fa=nq;
for(;p&&pool[p].ch[c]==q;p=pool[p].fa) pool[p].ch[c]=nq;//以上为case 3
}
}
}

广义后缀自动机

广义后缀自动机就是把所有串放到一个后缀自动机里一起处理。
每次往后缀自动机里添加一个字符串后,将 $last$ 重新挪到 $1$ 号节点上,再添加下一个字符串即可。添加字符串 $S_i$ 时,添加出来的所有实节点及它们在 $pre$ 树上的祖先们代表的子串,都在字符串 $S_i$ 中出现过。于是我们可以利用一些类似于 $set$ 启发式合并的方法,来得到每个节点在多少个字符串中出现过。

应用

  • 本质不同回文串

  • manacher & hash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
int t1[N],t2[N],c[N],srank[N],height[N],sa[N];
bool cmp(int *r,int a,int b,int l){
return r[a] == r[b] && r[a+l] == r[b+l];
}
void DA(int str[],int n,int m){//m为字符串中的最大值+1
n++;
int i, j, p, *x = t1, *y = t2;
//第一轮基数排序,如果s 的最大值很大,可改为快速排序
for(i = 0;i < m;i++)c[i] = 0;
for(i = 0;i < n;i++)c[x[i] = str[i]]++;
for(i = 1;i < m;i++)c[i] += c[i-1];
for(i = n-1;i >= 0;i--)sa[--c[x[i]]] = i;
for(j = 1;j <= n; j <<= 1){
p = 0;
//直接利用sa 数组排序第二关键字
for(i = n-j; i < n; i++)y[p++] = i;//后面的j 个数第二关键字为空的最小
for(i = 0; i < n; i++)if(sa[i] >= j)y[p++] = sa[i] - j;//这样数组y保存的就是按照第二关键字排序的结果
//基数排序第一关键字
for(i = 0; i < m; i++)c[i] = 0;
for(i = 0; i < n; i++)c[x[y[i]]]++;
for(i = 1; i < m;i++)c[i] += c[i-1];
for(i = n-1; i >= 0;i--)sa[--c[x[y[i]]]] = y[i];//根据sa 和x 数组计算新的x 数组
swap(x,y);
p = 1; x[sa[0]] = 0;
for(i = 1;i < n;i++)x[sa[i]] = cmp(y,sa[i-1],sa[i],j)?p-1:p++;
if(p >= n)break;
m = p;//下次基数排序的最大值
}
int k = 0;
n--;
for(i = 0;i <= n;i++)srank[sa[i]] = i;
for(i = 0;i < n;i++){
if(k)k--;
j = sa[srank[i]-1];
while(str[i+k] == str[j+k])k++;
height[srank[i]] = k;
}
}
int r[N];

int ans[M];
ull P = 1313131;
ull sqr[M],has[M];
unordered_set<ull>ss;
vector<pii> v;
ull getHash(int x,int y){
return has[y]-has[x-1]*sqr[y-x+1];
}


void init(char *s,int Len){//s 从1开始
sqr[0]=1;
for(int i=1;i<=Len;i++){
sqr[i]=sqr[i-1]*P;
has[i]=has[i-1]*P+s[i];
}
}
char s[M];

int dp[M][30];
void ST(int n) {
for (int i = 1; i <= n; i++) dp[i][0] = height[i];
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
dp[i][j] = min(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
}
}
}
int RMQ(int l, int r) {
int k = 0;
while ((1 << (k + 1)) <= r - l + 1) k++;
return min(dp[l][k], dp[r - (1 << k) + 1][k]);
}
int len;
void query(int x,int y){
// printf("%d %d\n",x,y);
pii tmp(x,y);
int dep=srank[tmp.fi];
int l=2,r=dep,mid,tans=1;
if(l<=dep){
while(l<=r){
mid=(l+r)/2;
if(RMQ(mid,dep)<tmp.se)l=mid+1;
else r=mid-1;
}
tans+=dep-l+1;
}
l=dep+1,r=len;
while(l<=r){
mid=(l+r)/2;
if(RMQ(dep+1,mid)>=tmp.se)l=mid+1;
else r=mid-1;
}
tans+=r-dep;
ans[tmp.se]+=tans;
}

int insert(int x,int y){
if(!ss.count(getHash(x,y))){
ss.insert(getHash(x,y));
if((y-x+1)%2){
if(getHash(x,(x+y)/2)==getHash((x+y)/2,y))query(x-1,y-x+1);
}else{
if(getHash(x,(x+y)/2)==getHash((x+y)/2+1,y))query(x-1,y-x+1);
}
}
return 0;
}
void Manacher(char *s, int len) {
int r[M];
int R = 0, id = 0;
for (int i = 1; i <=len; i++) {
r[i] = R>i ? min(r[2 * id - i], R - i):insert(i,i); //r[i] 表示以 i 为中心的回文串半径(不包括 i )
while (i+r[i]+1<=len&&s[i - r[i] - 1]==s[i + r[i] + 1] ){
insert(i - r[i] - 1,i + r[i] + 1);
r[i]++;
}
if (i + r[i] > R) {
R = i + r[i];
id = i;
}
}
id=R=0;
for(int i=2;i<=len;i++){
r[i] = R>i ? min(r[2 * id - i], R - i + 1):0;//r[i] 表示以 i-1 和 i 为中心的回文串半径
while (i+r[i]<=len&&s[i - r[i] - 1]==s[i + r[i]]){
insert(i - r[i] - 1,i + r[i]);
r[i]++;
}
if (i + r[i]-1 > R) {
R = i + r[i]-1;
id = i;
}
}
}
int main() {
whiel(~in(s+1)){
len=strlen(s+1);
init(s,len);
for1(i,len){
r[i-1]=s[i]-'a'+1;
}
r[len]=0;
DA(r,len,30);
ST(len);
Manacher(s,len);
for1(i,len){
out(ans[i]);
ans[i]=0;
}
ss.clear();
puts("");
}
cerr<<clock()<<endl;
return 0;
}