「多串最长公共子串」SP1812 LCS2 - Longest Common Substring II

知识点: SAM

Luogu

题意简述:

给定不超过 \(10\) 个字符串 \(S_1, S_2, \cdots\),求它们最长公共子串的长度。
\(|S_i|\le 10^5\)


分析题意

多串最长公共子串问题,考虑 SAM。

如果只有两个串:SP1811 LCS - Longest Common Substring

对第一个串建 SAM,用第二个串从起始节点开始,在 SAM 上进行匹配。

若当前状态为 \(x\),如果有对应字符 \(s_i\) 的转移,直接转移即可,匹配长度 \(+1\)
如果没有对应转移,转移到 \(\operatorname{link}(x)\),匹配长度 \(=\operatorname{len}(x)+1\) 检查有无对应转移,若没有则继续转移到 \(\operatorname{link}(\operatorname{link}(x))\),直到存在对应转移。
若始终找不到对应转移,则从根开始重新匹配。

跳 parnet 树相当于失配指针,继续利用了已匹配的部分。
匹配过程中匹配的最长长度即为答案。


考虑多串,对第一个串 \(S_1\) 建 SAM,用其他串在 SAM 上匹配,设当前匹配到串 \(S_i\)
对于状态 \(u\),维护转移到它时最大的匹配长度 \(mx_u\),即以该状态作为后缀时的公共子串的最长长度。
在匹配过程中进行维护即可。

考虑一个状态 parent 树上的所有祖先,若该状态可被匹配到,则祖先也可被匹配到。
祖先的 \(mx\) 应为 其子树中 \(mx\) 的最大值。
\(S_i\) 匹配完成后按拓扑序对祖先的信息进行更新。

对于一个状态 \(u\),将 \(S_2\cdots S_n\) 匹配时的 \(mx_u\)\(\min\),得到在所有字符串中 转移到 \(u\) 最长的匹配长度,即以 \(u\) 为后缀时 \(S_2\cdots S_n\) 公共子串的最长长度,设为 \(mi_u\)

所有的 \(mi_u\) 取最大值,即为答案。


小细节

最长公共子串不会超过最短串的长度,应对最短的串建 SAM,以保证复杂度。
注意每次匹配完一个字串时,都将 \(mx\) 清空。
更新祖先信息时 应对祖先的 \(\operatorname{len}\)\(\min\)


代码实现

//知识点:SAM
/*
By:Luckyblock
*/
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
#define ll long long
const int kMaxn = 3e5 + 10;
const int kMaxm = 26;
//=============================================================
char S[11][kMaxn];
int n, T, last = 1, node_num = 1, ans;
int cnt[kMaxn], id[kMaxn];
int ch[kMaxn << 1][kMaxm], len[kMaxn <<1], link[kMaxn << 1];
int minn[kMaxn << 1], maxx[kMaxn << 1];
//=============================================================
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;
}
void GetMin(int &fir, int sec) {
  if (sec < fir) fir = sec;
}
void Insert(int c_) {
  int p = last, now = last = ++ node_num;
  len[now] = len[p] + 1;
  for (; p && ! ch[p][c_]; p = link[p]) ch[p][c_] = now;
  if (! p) {link[now] = 1; return ;} 
  int q = ch[p][c_];
  if (len[q] == len[p] + 1) {link[now] = q; return ;}
  int newq = ++ node_num;
  memcpy(ch[newq], ch[q], sizeof(ch[q]));  
  link[newq] = link[q], len[newq] = len[p] + 1; 
  link[q] = link[now] = newq; 
  for (; p && ch[p][c_] == q; p = link[p]) ch[p][c_] = newq;
}
void TopSort() {
  for (int i = 1; i <= node_num; ++ i) cnt[len[i]] ++;
  for (int i = 1; i <= node_num; ++ i) cnt[i] += cnt[i - 1];
  for (int i = 1; i <= node_num; ++ i) id[cnt[len[i]] --] = i;
}
void Work(char *S_) {
  int n = strlen(S_ + 1), now = 1, l = 0;
  for (int i = 1; i <= n; ++ i) {
    while (now && ! ch[now][S_[i] - 'a']) {
      now = link[now];
      l = len[now]; 
    }
    if (! now) {
      now = 1;
      l = 0;
      continue ;
    }
    ++ l;
    now = ch[now][S_[i] - 'a'];
    GetMax(maxx[now], l);
  }
  for (int i = node_num; i; -- i) {
    int u = id[i], fa = link[u];
    GetMax(maxx[fa], std :: min(maxx[u], len[fa]));
    GetMin(minn[u], maxx[u]); 
    maxx[u] = 0; //注意清空,准备下一次匹配。 
  }
}
//=============================================================
int main() {
  int min_len = 1e9 + 2077, min_pos;
  while (~ scanf("%s", S[++ T] + 1)) {
    int now_len = strlen(S[T] + 1);
    if (now_len < min_len) {
      min_len = now_len;
      min_pos = T;
    }
  }
  std :: swap(S[1], S[min_pos]);
  for (int i = 1; i <= min_len; ++ i) Insert(S[1][i] - 'a');
  TopSort();
  memset(minn, 63, sizeof (minn));
  for (int i = 2; i < T; ++ i) Work(S[i]);
  for (int i = 1; i <= node_num; ++ i) GetMax(ans, minn[i]);
  printf("%d", ans);
  return 0; 
}
posted @ 2020-08-19 16:46  Luckyblock  阅读(186)  评论(1编辑  收藏  举报