题目类型 后缀数组
题目意思
问两个最长 1e5 的字符串的最长公共子串
解题方法
在第一个字符串后添加一个区别于输入的所有字符的字符(例如 '#') 后再把第二个字符串添加在后面 构成一个新的字符
求这个新字符串的 height 数组
二分最长公共子串的长度然后判断是否可行 假设当前判断的长度为 mid
判断方法是 从小到大扫一次 height 数组 对于某几个连续的且大于 mid 的数 意味着这几个连续的数对应的公共前缀两两之间的最长公共前缀都是大于mid的 所以只要{
(所有这些后缀的左端点中最小值) <= (第一个字符串长度-mid)}且{
(所有这些后缀左端点中最大值 > (第一个字符串长度)}
即表示有两个后缀的最长公共子串大于 mid 且分别属于输入的两个字符串
参考代码 - 有疑问的地方在下方留言 看到会尽快回复的
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
#define B printf("BUG\n");
const int maxn = 1e5 + 10;
const int INF = 1<<29;
char s1[maxn*2], s2[maxn];
int len;
int height[maxn*2], sa[maxn*2], h[maxn*2], rank[maxn*2];
int c[maxn*2], x[maxn*2], y[maxn*2], sa2[maxn*2];
void get_height() {
h[0] = 0;
if(rank[0] != 0) {
int i = 0, j = sa[rank[0]-1];
while(j<len && s1[i] == s1[j]) { h[0]++; i++; j++; }
}
for( int i=1; i<len; i++ ) {
h[i] = (h[i-1] > 1 ? h[i-1] : 1) - 1;
if(rank[i] == 0) {
h[i] = 0;
continue;
}
int j = i+h[i], k = sa[rank[i]-1] + h[i];
while(j < len && k <len && s1[j] == s1[k]) { h[i]++; j++; k++; }
}
for( int i=0; i<len; i++ ) {
height[rank[i]] = h[i];
}
}
void get_sa() {
for( int i=0; i<len; i++ ) {
if(s1[i] == '#') x[i] = 1;
else x[i] = s1[i] - 'a'+2;
}
int dn = 1; // 倍增的当前长度 -> 从某一位开始 dn 长的串
// x[i] : 第 i 个前缀的权值
// sa[i] : 排第 i 的是哪个前缀
while(1) {
memset(c, 0, sizeof(c));
int nMAX = 0;
for( int i=len-dn; i<len; i++ ) y[i] = 0;
for( int i=0; i<len-dn; i++ ) { y[i] = x[i+dn]; nMAX = max(nMAX, y[i]); }
memset(c, 0, sizeof(c));
for( int i=0; i<len; i++ ) c[y[i]]++;
for( int i=1; i<=nMAX; i++ ) c[i] += c[i-1];
for( int i=len-1; i>=0; i-- ) {
sa[--c[y[i]]] = i;
}
memset(c, 0, sizeof(c));
for( int i=0; i<len; i++ ) c[x[sa[i]]]++;
for( int i=0; i<len; i++ ) nMAX = max(nMAX, x[i]);
for( int i=1; i<=nMAX; i++ ) c[i] += c[i-1];
for( int i=len-1; i>=0; i-- ) {
sa2[--c[x[sa[i]]]] = sa[i];
}
for( int i=0; i<len; i++ ) sa[i] = sa2[i];
c[sa[0]] = 1;
int rk = 1;
for( int i=1; i<len; i++ ) {
if(x[sa[i]] == x[sa[i-1]] && y[sa[i]] == y[sa[i-1]]) {
c[sa[i]] = rk;
}
else c[sa[i]] = ++rk;
}
if(rk == len) break;
for( int i=0; i<len; i++ ) x[i] = c[i];
dn *= 2;
}
for( int i=0; i<len; i++ ) rank[sa[i]] = i;
}
bool judge(int l, int len1, int len2) {
int nmin = INF, nmax = -INF;
for( int i=1; i<len; i++ ) {
if(height[i] >= l) {
nmin = min(nmin, min(sa[i], sa[i-1]));
nmax = max(nmax, max(sa[i], sa[i-1]));
if(nmin <= len1-l && nmax > len1) return true;
}
else { nmin = INF; nmax = -INF; }
}
return false;
}
int main() {
//freopen("in", "r", stdin);
while(scanf("%s%s", s1, s2) != EOF) {
int len1 = strlen(s1), len2 = strlen(s2);
len = len1;
s1[len++] = '#';
for( int i=0; i<len2; i++ ) s1[len++] = s2[i];
s1[len] = '\0';
get_sa();
get_height();
int L = 1, R = min(len1, len2);
int ans = 0;
int cnt = 0;
while(L <= R) {
int mid = (L+R)/2;
if(judge(mid, len1, len2)) {
ans = mid;
L = mid + 1;
}
else R = mid - 1;
}
printf("%d\n", ans);
}
return 0;
}