兩題本質相同:給定字符串(或兩個字符串),求最長出現至少兩次的子串長度(POJ 只需長度,URAL 需輸出任一子串)。等價於求最長公共子串(單串視為與自身匹配,排除完全重疊)。
SA:所有後綴按字典序排序的起始下標。Height[i] = LCP(SA[i], SA[i-1]):相鄰後綴的最長公共前綴。max(Height[i])(單串)或跨兩串的最大 Height(雙串)。Height[rank[i]] ≥ Height[rank[i-1]] - 1。#includeusing namespace std; const int MAXN = 200050; int wa[MAXN], wb[MAXN], wv[MAXN], ws[MAXN]; int sa[MAXN], rnk[MAXN], height[MAXN]; int dp[MAXN][20], lg[MAXN]; char str1[MAXN], str2[MAXN]; int s[MAXN * 2]; // 倍增法構建後綴數組 void da(int *r, int *sa, int n, int m) { int *x = wa, *y = wb, *t; for (int i = 0; i < m; ++i) ws[i] = 0; for (int i = 0; i < n; ++i) ++ws[x[i] = r[i]]; for (int i = 1; i < m; ++i) ws[i] += ws[i-1]; for (int i = n-1; i >= 0; --i) sa[--ws[x[i]]] = i; for (int j = 1, p = 1; p < n; j <<= 1, m = p) { p = 0; for (int i = n-j; i < n; ++i) y[p++] = i; for (int i = 0; i < n; ++i) if (sa[i] >= j) y[p++] = sa[i] - j; for (int i = 0; i < n; ++i) wv[i] = x[y[i]]; for (int i = 0; i < m; ++i) ws[i] = 0; for (int i = 0; i < n; ++i) ++ws[wv[i]]; for (int i = 1; i < m; ++i) ws[i] += ws[i-1]; for (int i = n-1; i >= 0; --i) sa[--ws[wv[i]]] = y[i]; t = x; x = y; y = t; p = 1; x[sa[0]] = 0; for (int i = 1; i < n; ++i) x[sa[i]] = (y[sa[i-1]] == y[sa[i]] && y[sa[i-1]+j] == y[sa[i]+j]) ? p-1 : p++; } } // Kasiski 求 Height void calHeight(int *r, int *sa, int n) { for (int i = 1; i <= n; ++i) rnk[sa[i]] = i; for (int i = 0, k = 0; i < n; height[rnk[i++]] = k) for (k ? --k : 0, int j = sa[rnk[i]-1]; r[i+k] == r[j+k]; ++k); } // RMQ 預處理 void initRMQ(int n) { for (int i = 2; i <= n; ++i) lg[i] = lg[i>>1] + 1; for (int i = 1; i <= n; ++i) dp[i][0] = height[i]; for (int j = 1; (1< len1) || (a > len1 && b < len1)) // 跨兩串 ans = max(ans, height[i]); } printf("%d\n", ans); } return 0; }
後綴數組是解決「最長重複/公共子串」的標準重型武器。掌握 DA倍增構建 + Kasiski Height + RMQ 三部曲,可解決 POJ 2774、URAL 1517、HDU 3374 等經典題。注意分隔符選擇(必須小於所有字符且不出現在原串中)。