前言

如果你对这篇文章可感兴趣,可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」,查看完整博客分类与对应链接。

后缀数组概述

一、适用问题

后缀数组的题目非常灵活多变,主要涉及字符串所有后缀的字典序比较以及最长公共前缀。

本文主要介绍后缀数组的一些经典应用,虽然是经典应用,但是其思想应该属于后缀数组类问题的本质思想。

二、算法介绍

求解后缀数组的算法主要有倍增法、DC3DC3DC3 算法,具体的算法实现此处就略过了,想要具体了解的话可以自行 googlegooglegoogle 搜索。

下文给出的后缀数组模板是 DC3DC3DC3 算法,时间复杂度为 O(n)O(n)O(n)。

后缀数组中各大数组介绍

  • suffix[i]:suffix[i]:suffix[i]: 以 iii 为起始位置的后缀
  • sa[i]:sa[i]:sa[i]: 排名第 iii 的后缀的起始位置
  • rak[i]:rak[i]:rak[i]: 表示 suffix[i]suffix[i]suffix[i] 的排名
  • height[i]:height[i]:height[i]: suffix(sa[i−1])suffix(sa[i-1])suffix(sa[i−1]) 和 suffix(sa[i])suffix(sa[i])suffix(sa[i]) 的最长公共前缀
  • h[i]=height[rak[i]]h[i] = height[rak[i]]h[i]=height[rak[i]],h[i]≥h[i−1]−1h[i] \geq h[i-1]-1h[i]≥h[i−1]−1

有了上述数组,我们便可以求出字符串任意两个后缀的最长公共前缀。

  • suffix[i]suffix[i]suffix[i] 和 suffix[j]suffix[j]suffix[j] 之间的最长公共前缀 =min(height[rak[i]+1],...,height[rak[j]])= min(height[rak[i]+1],...,height[rak[j]])=min(height[rak[i]+1],...,height[rak[j]])

下图是一个字符串的所有后缀排序后的例子,可以根据这个例子来进一步理解这些数组。

后缀数组所有的题目都是在这几个数组上做文章,具体的题目类型分类见下文例题。

三、后缀数组模板

该模板中所有数组均从 000 开始计数。

#include <bits/stdc++.h>
const int N = 1e6+10;
using namespace std;int height[N],sa[N],rak[N],len;
/*suffix[i]: 以i为起始位置的后缀sa[i]: 排名第i的后缀的起始位置rak[i]: 表示suffix[i]的排名height[i]: suffix(sa[i-1])和suffix(sa[i])的最长公共前缀· h[i] = height[rak[i]], h[i] >= h[i-1]-1· suffix[i]和suffix[j]之间的最长公共前缀 = min(height[rak[i]+1]...height[rak[j]])
*/bool cmp(int *y,int a,int b,int k)
{int a1 = y[a],b1 = y[b];int a2 = a + k >= len ? -1 : y[a + k];int b2 = b + k >= len ? -1 : y[b + k];return a1 == b1 && a2 == b2;
}int t1[N],t2[N],cc[N];void get_sa(char s[])
{int *x = t1,*y = t2,m = 200;for(int i = 0;i < m;i ++) cc[i] = 0;for(int i = 0;i < len;i ++) ++ cc[x[i] = s[i]];for(int i = 1;i < m;i ++) cc[i] += cc[i - 1];for(int i = len - 1;~i;i --) sa[-- cc[x[i]]] = i;for(int k = 1;k < len;k <<= 1){int p = 0;for(int i = len - k;i < len;i ++)  y[p ++] = i;for(int i = 0;i < len;i ++) if(sa[i] >= k) y[p ++] = sa[i] - k;for(int i = 0;i < m;i ++) cc[i] = 0;for(int i = 0;i < len;i ++) ++ cc[x[y[i]]];for(int i = 1;i < m;i ++) cc[i] += cc[i - 1];for(int i = len - 1;~i;i --) sa[-- cc[x[y[i]]]] = y[i];swap(x,y); m = 1; x[sa[0]] = 0;for(int i = 1;i < len;i ++)x[sa[i]] = cmp(y,sa[i - 1],sa[i],k) ? m - 1 : m ++;if(m >= len) break;}
}void get_height(char s[])
{for(int i = 0;i < len;i ++) rak[sa[i]] = i;int h = 0;height[0] = 0;for(int i = 0;i < len;i ++){if(!rak[i]) continue;int j = sa[rak[i] - 1];if(h) h --;while(s[i + h] == s[j + h]) h ++;height[rak[i]] = h;}
}char s[N];int main(){scanf("%s",s);len = strlen(s);get_sa(s);get_height(s);return 0;
}

后缀数组系列习题

一、单字符串问题

模板题

1.最长公共前缀

题意: 给定一个字符串,询问某两个后缀的最长公共前缀。

思路: 模板题。对字符串求出后缀数组后,用线段树或 ststst 表进行 rmqrmqrmq 即可。

重复子串问题【两个子串】

2.可重叠最长重复子串

题意: 给定一个字符串,求最长重复子串,这两个子串可以重叠。

思路: 模板题。即求字符串中任意两个后缀最长公共前缀的最大值,即输出 heightheightheight 数组中的最大值即可。

3.不可重叠最长重复子串

题意: 给定一个字符串,求最长不可重复子串,这两个子串不可以重叠。

思路: 这个题与上面那个题最大的区别在于,此题找到的两个子串不可以重叠。因此我们在此处引入后缀数组中一个重要的思想,二分 +height+\ height+ height 分组。

我们二分一个答案 www,然后根据这个答案对 heightheightheight 数组进行分组,即每一组中的 heightheightheight 数组值大于 www。然后记录一个 maxxmaxxmaxx 和 minnminnminn 分别表示该组中所有后缀起始位置的最大值和最小值,然后判断两个位置之间的差是否大于等于 www,如果大于等于则 www 可行。

4.可重叠的 kkk 次最长重复子串

题意: 给定一个字符串,求至少出现 kkk 次的最长重复子串,这 kkk 个子串可以重叠。

思路: 利用上面那个题的 二分 +height+\ height+ height 分组 思想可以轻松解决此题。即分完组之后判断该组内的字符串数是否大于 www 即可。

不相同子串个数

5.不相同的子串的个数

题意: 给定一个字符串,求不相同的子串个数。

思路: 求出该字符串的后缀数组,然后遍历 heightheightheight 数组,排名为 iii 的后缀的起始位置为 sa[i]sa[i]sa[i],如果可以相同的话,对答案的贡献为 n−sa[i]+1n-sa[i]+1n−sa[i]+1。但是现在要求不相同的子串个数,因此要减去最长的相同前缀,即贡献为 n−sa[i]+1−height[i]n-sa[i]+1-height[i]n−sa[i]+1−height[i]。

字符串由连续重复子串构成

6.连续重复子串 [整个串]

题意: 给定一个字符串 LLL,已知这个字符串是由某个字符串 SSS 重复 RRR 次得到的,求 RRR 的最大值。

思路: 设 LENLENLEN 为字符串 LLL 的长度。枚举字符串 SSS 的长度 lenlenlen,保证 lenlenlen 能够被 LENLENLEN 整除。然后再判断 suffix[0]suffix[0]suffix[0] 与 suffix[len]suffix[len]suffix[len] 的最长公共前缀长度是否为 LEN−lenLEN-lenLEN−len,如果是则表示该长度符合答案。

这里求后缀最长公共前缀时不需要 rmqrmqrmq,由于 suffix[0]suffix[0]suffix[0] 是固定的,因此只需要求出 height[i]height[i]height[i] 数组到 height[rak[0]]height[rak[0]]height[rak[0]] 的最小值即可,总时间复杂度为 O(n)O(n)O(n)。

7.重复次数最多的连续重复子串 [子串]

题意: 给定一个字符串 LLL,在该字符串的所有子串中找到一个重复度最多的子串,重复度相同则输出字典序最小。重复度即上题定义的重复次数,例如 “ababababababababab” 重复次数为 333,"abcdabcdabcd" 重复次数为 111。

思路: 我们枚举 lenlenlen,去求解长度为 lenlenlen 下最大的重复度 RRR,即有无一个子串由 RRR 个长度为 lenlenlen 的子串连续拼接而成。

因此我们将字符串 LLL 分段,0、len、2∗len、3∗len、...、k∗len0、len、2*len、3*len、...、k*len0、len、2∗len、3∗len、...、k∗len,对于 suffix[i∗len]suffix[i*len]suffix[i∗len] 与 suffix[(i+1)∗len]suffix[(i+1)*len]suffix[(i+1)∗len] 求出最长公共前缀 xxx,再另 pos=i∗len−(len−x%len)pos=i*len-(len-x\%len)pos=i∗len−(len−x%len),求 suffix[pos]suffix[pos]suffix[pos] 与 suffix[pos+len]suffix[pos+len]suffix[pos+len] 的最长公共前缀,然后更新答案。

这样操作的原因其实是枚举所有的以长度 lenlenlen 进行重复的子串,这也是对 lenlenlen 取模数的原因。可能这样说还是有些难以理解,推荐自己静下来想一想,或者看看代码。

最后分析下时间复杂度,n1+n2+n3+...+nn=O(nlogn)\displaystyle\frac{n}{1}+\displaystyle\frac{n}{2}+\displaystyle\frac{n}{3}+...+\displaystyle\frac{n}{n}=O(nlogn)1n​+2n​+3n​+...+nn​=O(nlogn)。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define mem(a,b) memset(a,b,sizeof a);
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define per(i,a,b) for(int i = a; i >= b; i--)
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
typedef long long ll;
typedef double db;
const int N = 1e5+10;
const db EPS = 1e-9;
using namespace std;void dbg() {cout << "\n";}
template<typename T, typename... A> void dbg(T a, A... x) {cout << a << ' '; dbg(x...);}
#define logs(x...) {cout << #x << " -> "; dbg(x);}int n,height[N],sa[N],rak[N],len; //所有数组都从0开始计数
/*suffix[i]: 以i为起始位置的后缀sa[i]: 排名第i的后缀的起始位置rak[i]: 表示suffix[i]的排名height[i]: suffix(sa[i-1])和suffix(sa[i])的最长公共前缀· h[i] = height[rak[i]], h[i] >= h[i-1]-1· suffix[i]和suffix[j]之前的最长公共前缀 = min(height[rak[i]+1]...height[rak[j]])
*/bool cmp(int *y,int a,int b,int k)
{int a1 = y[a],b1 = y[b];int a2 = a + k >= len ? -1 : y[a + k];int b2 = b + k >= len ? -1 : y[b + k];return a1 == b1 && a2 == b2;
}int t1[N],t2[N],cc[N];void get_sa(char s[])
{int *x = t1,*y = t2,m = 200;len = strlen(s);for(int i = 0;i < m;i ++) cc[i] = 0;for(int i = 0;i < len;i ++) ++ cc[x[i] = s[i]];for(int i = 1;i < m;i ++) cc[i] += cc[i - 1];for(int i = len - 1;~i;i --) sa[-- cc[x[i]]] = i;for(int k = 1;k < len;k <<= 1){int p = 0;for(int i = len - k;i < len;i ++)  y[p ++] = i;for(int i = 0;i < len;i ++) if(sa[i] >= k) y[p ++] = sa[i] - k;for(int i = 0;i < m;i ++) cc[i] = 0;for(int i = 0;i < len;i ++) ++ cc[x[y[i]]];for(int i = 1;i < m;i ++) cc[i] += cc[i - 1];for(int i = len - 1;~i;i --) sa[-- cc[x[y[i]]]] = y[i];swap(x,y); m = 1; x[sa[0]] = 0;for(int i = 1;i < len;i ++)x[sa[i]] = cmp(y,sa[i - 1],sa[i],k) ? m - 1 : m ++;if(m >= len) break;}
}void get_height(char s[])
{len = strlen(s);for(int i = 0;i < len;i ++) rak[sa[i]] = i;int h = 0;height[0] = 0;for(int i = 0;i < len;i ++){if(!rak[i]) continue;int j = sa[rak[i] - 1];if(h) h --;while(s[i + h] == s[j + h]) h ++;height[rak[i]] = h;}
}char s[N];int st[N][20];void init(){for(int i = 0; i < len; i++) st[i][0] = height[i];for(int j = 1; (1<<j) <= len; j++){for(int i = 0; i + (1<<j) - 1 < len; i++)st[i][j] = min(st[i][j-1],st[i+(1<<(j-1))][j-1]);}
}int query(int l,int r){int k = (int)(log((double)(r - l + 1)) / log(2.0));return min(st[l][k],st[r-(1<<k)+1][k]);
}int main(){int _ = 0;while(~scanf("%s",s)){if(s[0] == '#') break;len = strlen(s);get_sa(s);get_height(s);init();int ans = 0, LEN = 0;rep(L,1,len){for(int j = L; j < len; j += L){int p1 = rak[j];int p2 = rak[j-L];if(p1 > p2) swap(p1,p2);int thp = query(p1+1,p2);if((thp/L)+1 > ans)ans = (thp/L)+1, LEN = L;thp = L-thp%L;if(j-L-thp < 0) continue;p1 = rak[j-thp], p2 = rak[j-L-thp];if(p1 > p2) swap(p1,p2);thp = query(p1+1,p2);if((thp/L)+1 > ans)ans = (thp/L)+1, LEN = L;}}int pos = -1;rep(i,0,len-1-LEN){int p1 = rak[i], p2 = rak[i+LEN];if(p1 > p2) swap(p1,p2);int thp = query(p1+1,p2);if((thp/LEN)+1 == ans){if(pos == -1) pos = i;else if(rak[i] < rak[pos]) pos = i;}}s[pos+LEN*ans] = '\0';printf("Case %d: %s\n",++_,s+pos);}return 0;
}

二、双字符串问题

双字符串拼接问题

8.最长公共子串

题意: 给定两个字符串 AAA 和 BBB,求最长公共子串。

思路: 这里介绍一下后缀数组的另一个套路,即涉及两个或多个串时,往往会将两个串用一个没出现的符号(比如 ‘$’),将两个串连接在一起。

比如此题即可将两个串拼接在一起,求出后缀数组之后,枚举 heightheightheight 数组,判断 heightheightheight 数组中相邻的两个后缀是否来自不同的串,如果是则更新答案。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define mem(a,b) memset(a,b,sizeof a);
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define per(i,a,b) for(int i = a; i >= b; i--)
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
typedef long long ll;
typedef double db;
const int N = 2*1e5+10;
const db EPS = 1e-9;
using namespace std;void dbg() {cout << "\n";}
template<typename T, typename... A> void dbg(T a, A... x) {cout << a << ' '; dbg(x...);}
#define logs(x...) {cout << #x << " -> "; dbg(x);}int n,height[N],sa[N],rak[N],len; //所有数组都从0开始计数
/*suffix[i]: 以i为起始位置的后缀sa[i]: 排名第i的后缀的起始位置rak[i]: 表示suffix[i]的排名height[i]: suffix(sa[i-1])和suffix(sa[i])的最长公共前缀· h[i] = height[rak[i]], h[i] >= h[i-1]-1· suffix[i]和suffix[j]之前的最长公共前缀 = min(height[rak[i]+1]...height[rak[j]])
*/bool cmp(int *y,int a,int b,int k)
{int a1 = y[a],b1 = y[b];int a2 = a + k >= len ? -1 : y[a + k];int b2 = b + k >= len ? -1 : y[b + k];return a1 == b1 && a2 == b2;
}int t1[N],t2[N],cc[N];void get_sa(char s[])
{int *x = t1,*y = t2,m = 200;len = strlen(s);for(int i = 0;i < m;i ++) cc[i] = 0;for(int i = 0;i < len;i ++) ++ cc[x[i] = s[i]];for(int i = 1;i < m;i ++) cc[i] += cc[i - 1];for(int i = len - 1;~i;i --) sa[-- cc[x[i]]] = i;for(int k = 1;k < len;k <<= 1){int p = 0;for(int i = len - k;i < len;i ++)  y[p ++] = i;for(int i = 0;i < len;i ++) if(sa[i] >= k) y[p ++] = sa[i] - k;for(int i = 0;i < m;i ++) cc[i] = 0;for(int i = 0;i < len;i ++) ++ cc[x[y[i]]];for(int i = 1;i < m;i ++) cc[i] += cc[i - 1];for(int i = len - 1;~i;i --) sa[-- cc[x[y[i]]]] = y[i];swap(x,y); m = 1; x[sa[0]] = 0;for(int i = 1;i < len;i ++)x[sa[i]] = cmp(y,sa[i - 1],sa[i],k) ? m - 1 : m ++;if(m >= len) break;}
}void get_height(char s[])
{len = strlen(s);for(int i = 0;i < len;i ++) rak[sa[i]] = i;int h = 0;height[0] = 0;for(int i = 0;i < len;i ++){if(!rak[i]) continue;int j = sa[rak[i] - 1];if(h) h --;while(s[i + h] == s[j + h]) h ++;height[rak[i]] = h;}
}char s1[N],s2[N],s[N];int main(){scanf("%s",s1);scanf("%s",s2);int len1 = strlen(s1), len2 = strlen(s2);rep(i,0,len1-1) s[i] = s1[i];s[len1] = '$';rep(i,0,len2-1) s[i+len1+1] = s2[i];len = strlen(s);get_sa(s);get_height(s);//[0,len1-1] [len1+1,len1+len2]int ans = 0;rep(i,1,len1+len2){int p1 = sa[i], p2 = sa[i-1];if(p1 <= len1-1 && p2 >= len1+1) ans = max(ans,height[i]);else if(p2 <= len1-1 && p1 >= len1+1) ans = max(ans,height[i]);}printf("%d\n",ans);return 0;
}
9.长度不小于 kkk 的公共子串的个数

题意: 给定两个字符串 AAA 和 BBB,求长度不小于 kkk 的公共子串的个数,即询问有多少个三元组 (i,j,len)(i,j,len)(i,j,len) 表示 A[i]~A[i+len−1]A[i]~A[i+len-1]A[i]~A[i+len−1] 与 B[j]~B[j+len−1]B[j]~B[j+len-1]B[j]~B[j+len−1] 相同,且 len≥klen\geq klen≥k。

思路: 首先将两个串拼接起来,求出后缀数组之后,按 kkk 对 heightheightheight 数组进行分组。对于两个最长公共前缀为 hhh 的后缀,其对答案的贡献为 h−k+1h-k+1h−k+1,因此我们分组之后,先考虑 AAA 对 BBB 的贡献,维护一个单调栈,存储信息为 pair(int,int)pair(int,int)pair(int,int) 表示 heightheightheight 的值与个数,每当有更小的值进入时就不断向前合并,主要是 secondsecondsecond 的信息合并,并且记录一个全局的 total=∑first∗secondtotal=\sum first*secondtotal=∑first∗second,用于计算贡献。

之后再计算一遍 BBB 对 AAA 的贡献即可结束此题。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define mem(a,b) memset(a,b,sizeof a);
#define rep(i,a,b) for(int i = a; i <= b; i++)
#define per(i,a,b) for(int i = a; i >= b; i--)
#define __ ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
typedef long long ll;
typedef double db;
const int N = 2*1e5+10;
const db EPS = 1e-9;
using namespace std;void dbg() {cout << "\n";}
template<typename T, typename... A> void dbg(T a, A... x) {cout << a << ' '; dbg(x...);}
#define logs(x...) {cout << #x << " -> "; dbg(x);}int n,k,height[N],sa[N],rak[N],len; //所有数组都从0开始计数
/*suffix[i]: 以i为起始位置的后缀sa[i]: 排名第i的后缀的起始位置rak[i]: 表示suffix[i]的排名height[i]: suffix(sa[i-1])和suffix(sa[i])的最长公共前缀· h[i] = height[rak[i]], h[i] >= h[i-1]-1· suffix[i]和suffix[j]之前的最长公共前缀 = min(height[rak[i]+1]...height[rak[j]])
*/bool cmp(int *y,int a,int b,int k)
{int a1 = y[a],b1 = y[b];int a2 = a + k >= len ? -1 : y[a + k];int b2 = b + k >= len ? -1 : y[b + k];return a1 == b1 && a2 == b2;
}int t1[N],t2[N],cc[N];void get_sa(char s[])
{int *x = t1,*y = t2,m = 200;len = strlen(s);for(int i = 0;i < m;i ++) cc[i] = 0;for(int i = 0;i < len;i ++) ++ cc[x[i] = s[i]];for(int i = 1;i < m;i ++) cc[i] += cc[i - 1];for(int i = len - 1;~i;i --) sa[-- cc[x[i]]] = i;for(int k = 1;k < len;k <<= 1){int p = 0;for(int i = len - k;i < len;i ++)  y[p ++] = i;for(int i = 0;i < len;i ++) if(sa[i] >= k) y[p ++] = sa[i] - k;for(int i = 0;i < m;i ++) cc[i] = 0;for(int i = 0;i < len;i ++) ++ cc[x[y[i]]];for(int i = 1;i < m;i ++) cc[i] += cc[i - 1];for(int i = len - 1;~i;i --) sa[-- cc[x[y[i]]]] = y[i];swap(x,y); m = 1; x[sa[0]] = 0;for(int i = 1;i < len;i ++)x[sa[i]] = cmp(y,sa[i - 1],sa[i],k) ? m - 1 : m ++;if(m >= len) break;}
}void get_height(char s[])
{len = strlen(s);for(int i = 0;i < len;i ++) rak[sa[i]] = i;int h = 0;height[0] = 0;for(int i = 0;i < len;i ++){if(!rak[i]) continue;int j = sa[rak[i] - 1];if(h) h --;while(s[i + h] == s[j + h]) h ++;height[rak[i]] = h;}
}char s1[N],s2[N],s[N];int tot;
pair<int,int> st[N];int main(){while(~scanf("%d",&k)){if(k == 0) break;scanf("%s",s1);scanf("%s",s2);int len1 = strlen(s1), len2 = strlen(s2);rep(i,0,len1-1) s[i] = s1[i];s[len1] = '$';rep(i,0,len2-1) s[i+len1+1] = s2[i];len = strlen(s);get_sa(s);get_height(s);//A:[0,len1-1], B:[len1+1,len1+len2]ll ans = 0, total = 0;tot = -1;// rep(i,0,len1+len2) logs(i,sa[i],height[i]);rep(i,1,len1+len2){if(height[i] < k) {tot = -1; total = 0; continue;}else{// logs(i,sa[i]);int cnt = 0;if(sa[i-1] <= len1-1) cnt = 1;while(tot >= 0 && st[tot].first >= height[i]){total -= (ll)(st[tot].first-k+1)*(ll)st[tot].second;cnt += st[tot].second;tot--;}if(cnt != 0){st[++tot] = make_pair(height[i],cnt);total += (ll)(height[i]-k+1)*(ll)cnt;}if(sa[i] >= len1+1) ans += total;}}tot = -1; total = 0;rep(i,1,len1+len2){if(height[i] < k) {tot = -1; total = 0; continue;}else{int cnt = 0;if(sa[i-1] >= len1+1) cnt = 1;while(tot >= 0 && st[tot].first >= height[i]){total -= (ll)(st[tot].first-k+1)*(ll)st[tot].second;cnt += st[tot].second;tot--;}if(cnt != 0){st[++tot] = make_pair(height[i],cnt);total += (ll)(height[i]-k+1)*(ll)cnt;}// logs(i,sa[i],height[i],total);if(sa[i] <= len1-1) ans += total;}}printf("%lld\n",ans);rep(i,0,len1+len2) s[i] = '\0';}return 0;
}

三、nnn 字符串问题

10. 至少在 kkk 个字符串中出现的最长子串

题意: 给定 nnn 个字符串,求出现在不少于 kkk 个字符串中的最长子串。

思路: 首先将 nnn 个字符串拼接在一起,再二分答案进行 heightheightheight 分组,然后查看每一组中出现了多少个不同的字符串,如果大于等于 kkk 则符合题意。

11. 每个字符串至少出现两次且不重叠的最长子串

题意: 给定 nnn 个字符串,求在每个字符串中至少出现两次且不重叠的最长子串。

思路: 与上题没有太大的差别,还是先将 nnn 个字符串拼接在一起,再二分答案进行 heightheightheight 分组,记录每一组中出现位置的最大值和最小值,然后查看有多少组的最大值和最小值之差符合条件,如果大于等于 kkk 则符合题意。

后记


后缀数组的概述与习题到这里就结束了,总结一下这么多题目主要就是两个套路,一个是二分 +++ heightheightheight 分组,另一个是用没出现过的符号拼接所有字符串。除此之外涉及到的内容都属于数据结构问题的常见操作了。

本篇博客到这里就结束了,祝大家 ACACAC 愉快,一起爱上字符串把!(๑•̀ㅂ•́)و✧

ACM 的旅行虽然充满荆棘但一抬头便能看见无数束光,请务必坚持下去,负重前行终有云开雾散之日!

后缀数组算法概述及习题相关推荐

  1. F - Anti-Rhyme Pairs(rmq算法模板)(后缀数组算法模板)

    点击打开链接 题目大意:通常押韵的两个词以相同的字符结尾.我们运用这个特性来规定反押韵的概念.反押韵是一对拥有近似开头的单词.一对单词的反押韵的复杂度被定义为两者都以之开头且最长的字符串S的长度.因此 ...

  2. 算法学习:后缀数组 height的求取

    [前置知识] 后缀数组 [定义] [LCP]全名最长公共前缀,两个后缀之间的最长前缀,以下我们定义 lcp ( i , j ) 的意义是后缀 i 和 j 的最长前缀 [z函数] 函数z [ i ] 表 ...

  3. 算法学习:后缀数组(SA)

    [参考博客] https://xminh.github.io/2018/02/27/%E5%90%8E%E7%BC%80%E6%95%B0%E7%BB%84-%E6%9C%80%E8%AF%A6%E7 ...

  4. 算法竞赛进阶指南——后缀数组

    后缀数组 后缀数组 (SA) 是一种重要的数据结构,通常使用倍增或者DC3算法实现,这超出了我们的讨论范围. 在本题中,我们希望使用快排.Hash与二分实现一个简单的O(nlog2n)的后缀数组求法. ...

  5. 试题 算法训练 后缀数组——最长重复子串

    资源限制 时间限制:100ms 内存限制:256.0MB 问题描述 给定一个长度为n的数串,求至少出现k 次的最长重复子串的长度,这k 个子串可以重叠.保证有子串出现至少k次. 输入格式 第一行:两个 ...

  6. 算法竞赛进阶指南:0x14:后缀数组

    原题链接 后缀数组 (SA) 是一种重要的数据结构,通常使用倍增或者 DC3 算法实现,这超出了我们的讨论范围. 在本题中,我们希望使用快排.Hash 与二分实现一个简单的 O(nlog^2n) 的后 ...

  7. 【算法与数据结构】——后缀数组

    参考后缀数组 基数排序 后缀数组的实现用到了基数排序,简单介绍一下基数排序的内容. 基数排序是桶排序的一种扩展,是一种多关键字的排序方法.若记录按照多个关键字排序,则依次按照这些关键字进行排序. 例如 ...

  8. 长字符串匹配(BWT编码、后缀数组、倍增算法、FM索引)

    用 O(m) 时间复杂度找出一个长度为 m 的短字符串在一个长度为 n 的长字符串中的精确匹配(n>>m),限制长短字符串仅由 A.C.G.T 这四种字符组成. 输入:长短字符串 输出:短 ...

  9. 算法笔记——后缀数组

    后缀指从某个位置开始到字符串末尾的一个子串,用suffix(i)来表示. 后缀数组(suffix array)指将s的所有后缀从小到大排序后,取其下标i放入数组中,该数组就叫做后缀数组. 表示排名为i ...

  10. DC3算法(后缀数组生成)

    文章目录 后缀数组是什么? 后缀数组的地位 后缀数组的生成 基数排序 概念 算法流程 生成后缀数组 得到S12类的排名 解释 代码 后缀数组是什么? 如果我要对数组排序的话,按字符串的字典序来排,这个 ...

最新文章

  1. SYNCHRO 4D可视化调度学习教程 SYNCHRO 4D: Visual Scheduling
  2. 常用的css3的新属性
  3. 没想到我提前56年感受了赛博朋克
  4. 一线互联网智能推荐系统架构演进
  5. linux 单用户模式 救援模式 忘记root密码的两种解决办法
  6. Spring整合RabbitMQ
  7. 自学前端开发:想要学习成为一名优秀的前端开发者,代码之外需要关注的问题
  8. Spring+MyBatis多数据源配置实现
  9. ❤️Docker中只需2步即可拥有Oracle 10G环境,史上最快部署❤️
  10. (01)Structs初学笔记——开篇
  11. 金鳞化龙——AMD处理器“开核”大测试(图)
  12. CATIA如何实现设计模块快速切换?
  13. 【仿人机器人】机器人的数学建模基础
  14. 十三种Java开发工具
  15. 进博会中国自行车排名辐轮王自行车点赞中国GDP突破一百万亿元
  16. python处理图片文件,python 间接处理webp图片文件
  17. android so 瘦身,Android APK 瘦身实践
  18. BootStrap4工具类之阴影效果
  19. android 耳机孔 红外,手机遥控器,3.5mm耳机接口红外遥控改造解析
  20. CVPR 2021放榜,腾讯优图20篇论文都在这里了!

热门文章

  1. 面向对象语言-反射机制
  2. CSS中filter滤镜的学习笔记
  3. 1.4补充 三态缓存(tristate buffer)与 多路复用器(Multiplexers)
  4. STC学习:定时器和中断
  5. ba网络c语言编程,如何用C语言程序构造随机网络和BA无尺度网络
  6. phpstudy不执行php文件,phpstudy运行时突然无法报错
  7. maven依赖c3p0_springboot 使用c3p0数据库连接池的方法
  8. mysql demo_mysql 查询小demo
  9. NYOJ759 你知道这个规律吗
  10. 测试用例编号_如何编写一个规范的测试用例?你应该知道的!