1. 程式人生 > 實用技巧 >SP1811 LCS - Longest Common Substring

SP1811 LCS - Longest Common Substring

知識點: SA,單調棧

原題面 Luogu


題意簡述

給定兩字串 \(S_1, S_2\),求它們的最長公共子串長度。
\(|S_1|,|S_2|\le 2.5\times 10^5\)


分析題意

只有兩個字串,考慮 SA。
\(S_1\) 加個終止符,\(S_2\) 拼接到 \(S_1\) 後面,跑 SA 求出 \(\operatorname{height}\)
問題變為 求前半段字尾 與 後半段字尾 \(\operatorname{lcp}\) 的最大值。

\(i\) 為後半段的字尾 等價於 \(i>|S_1|+1\)
對於一個字尾 \(i>|S_1|+1\),設 \(l_i\)

為字尾排序後 最大\(i\)前半段 的字尾的 排名
即有 \(sa_{l_i}\le |S_1|,\ l+i<rk_i\),且 \(\forall l_i<k<rk_i, sa_k>|S_1|+1\) 成立。
類似地,設 \(r_i\) 為字尾排序後 最小\(i\)前半段 的字尾的 排名

考慮 \(\operatorname{lcp}\) 的單調性。
對於一個後半段的字尾 \(i>|S_1|+1\),滿足 \(\operatorname{lcp}(i,j)\) 最大的 \(j\le|S_1|\),顯然為 \(l_i\)

\(r_i\),有。

\[\max\{\operatorname{lcp}(i,j)\} = \max\{\operatorname{lcp}(l_i, i), \operatorname{lcp}(i, r_i)\} \]

則有:

\[ans = \max_{i=|S_1|+2}^{|S_1|+|S_2|+1}\max\{\operatorname{lcp}(l_i, i), \operatorname{lcp}(i, r_i)\} \]

先預處理 \(\operatorname{lcp}\) 建立 st 表。
\(l_i,r_i\) 可通過單調棧簡單求得,計算答案時列舉後半段字尾,\(O(1)\)

查詢 \(\operatorname{lcp}\) 即可。

總複雜度 \(O(n\log n)\) 級別。


一些細節

\(l_i<1\) 時,該 \(l_i\) 不作出貢獻,因為不存在這樣的字尾。
\(r_i>|S|\) 時也沒有貢獻,這樣的字尾已經屬於後半段了。


爆零小技巧:\(\log\) 函式賊 jier 慢,建議預處理 \(\log_2\) 函式。


程式碼實現

//知識點:SA,單調棧
/*
By:Luckyblock
*/
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <ctype.h>
#define ll long long
const int kMaxn = 5e5 + 10;
//=============================================================
char S[kMaxn];
int n1, n, m, ans, cnt[kMaxn], id[kMaxn], rkid[kMaxn];
int sa[kMaxn], rk[kMaxn << 1], oldrk[kMaxn << 1], height[kMaxn];
int MaxHeight[kMaxn][20], Log2[kMaxn];
int top, st[kMaxn], L[kMaxn], R[kMaxn];
//=============================================================
inline int read() {
  int f = 1, w = 0; char ch = getchar();
  for (; !isdigit(ch); ch = getchar()) if (ch == '-') f = -1;
  for (; isdigit(ch); ch = getchar()) w = (w << 3) + (w << 1) + (ch ^ '0');
  return f * w;
}
void GetMax(int &fir, int sec) {
  if (sec > fir) fir = sec;
}
bool cmp(int x, int y, int w) {
  return oldrk[x] == oldrk[y] && 
         oldrk[x + w] == oldrk[y + w]; 
}
void GetHeight() {
  for (int i = 1, k = 0; i <= n; ++ i) {
    if (rk[i] == 1) k = 0;
    else {
      if (k > 0) k --;
      int j = sa[rk[i] - 1];
      while (i + k <= n && j + k <= n && 
             S[i + k] == S[j + k]) {
        ++ k;
      }
    }
    height[rk[i]] = k;
  }
}
int Query(int l_, int r_) {
  int k = Log2[r_ - l_ + 1];
  return std :: min(MaxHeight[l_][k], MaxHeight[r_ - (1 << k) + 1][k]);
}
void MakeSt() {
  for (int i = 2; i <= n; ++ i) MaxHeight[i][0] = height[i];
  for (int i = 2; i <= n; ++ i) {
    Log2[i] = Log2[i - 1] + ((1 << Log2[i - 1] + 1) == i);
  }
  for (int j = 1; j < 20; ++ j) {
    for (int i = 1; i + (1 << j) - 1 <= n; ++ i) {
      MaxHeight[i][j] = std :: min(MaxHeight[i][j - 1], 
                                   MaxHeight[i + (1 << j - 1)][j - 1]);
    }
  }
}
void SuffixSort() {
  m = 300;
  for (int i = 1; i <= n; ++ i) ++ cnt[rk[i] = S[i]];
  for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
  for (int i = n; i >= 1; -- i) sa[cnt[rk[i]] --] = i;
  for (int p, w = 1; w < n; w <<= 1) {
    p = 0;
    for (int i = n; i > n - w; -- i) id[++ p] = i;
    for (int i = 1; i <= n; ++ i) {
      if (sa[i] > w) id[++ p] = sa[i] - w;
    }
    memset(cnt, 0, sizeof (cnt));
    for (int i = 1; i <= n; ++ i) ++ cnt[(rkid[i] = rk[id[i]])];
    for (int i = 1; i <= m; ++ i) cnt[i] += cnt[i - 1];
    for (int i = n; i >= 1; -- i) sa[cnt[rkid[i]] --] = id[i];
    std ::swap(rk, oldrk);
    m = 0;
    for (int i = 1; i <= n; ++ i) {
      m += (cmp(sa[i], sa[i - 1], w) ^ 1);
      rk[sa[i]] = m;
    }
  }
  GetHeight();
  MakeSt();
}
//=============================================================
int main() {
  scanf("%s", S + 1); n1 = strlen(S + 1);
  S[n1 + 1] = 'z' + 1;
  scanf("%s", S + n1 + 1 + 1); n = strlen(S + 1);
  SuffixSort();
  //注意 l,r 存的是排名,列舉時按照字典序列舉字尾。
  for (int i = 1, now = 0; i <= n; ++ i) {
    if (sa[i] == n1 + 1) continue ;
    if (sa[i] > n1 + 1) {
      L[i] = now, st[++ top] = i;
      continue ;
    }
    for (; top; top --) R[st[top]] = i;
    now = i;
  }
  for (; top; top --) R[st[top]] = n1 + 1;
  for (int i = 1; i <= n; ++ i) {
    if (sa[i] <= n1 + 1) continue ;
    if (L[i]) GetMax(ans, Query(L[i] + 1, i));
    if (R[i] <= n1 + 1) GetMax(ans, Query(i + 1, R[i]));
  }
  printf("%d", ans);
  return 0;
}
/*
aabcab
adadeaf

aabcab{adadeaf
abcab{adadeaf
ab{adadeaf
adadeaf
adeaf
af
bcab{adadeaf
b{adadeaf
cab{adadeaf
dadeaf
deaf
eaf
f
{adadeaf
*/