首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

POJ 2774 / URAL 1517 最長重複子串:後綴數組 + LCP + RMQ 完整解法

     編輯:關於C++

問題轉化

兩題本質相同:給定字符串(或兩個字符串),求最長出現至少兩次的子串長度(POJ 只需長度,URAL 需輸出任一子串)。等價於求最長公共子串(單串視為與自身匹配,排除完全重疊)。

核心算法:後綴數組 + Height 數組 + RMQ

  1. 構建後綴數組 SA:所有後綴按字典序排序的起始下標。
  2. 計算 Height[i] = LCP(SA[i], SA[i-1]):相鄰後綴的最長公共前綴。
  3. 答案即 max(Height[i])(單串)或跨兩串的最大 Height(雙串)。

關鍵技術點

  • 倍增法構建 SA:O(n log n),關鍵是基數排序(計數排序)實現 O(n) 排名更新。
  • Kasiski 算法求 Height:O(n),利用 Height[rank[i]] ≥ Height[rank[i-1]] - 1。
  • RMQ (稀疏表):O(1) 區間最小值查詢,輔助 URAL 輸出具體子串位置。

C++ 完整實現(POJ 2774)

#include 
using 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;
}

複雜度

  • 構建 SA:O(N log N),N = len1 + len2 ≤ 200,000。
  • Height + RMQ:O(N log N) 預處理,O(1) 查詢。
  • 總體輕鬆通過 4s/131MB 限制。

總結

後綴數組是解決「最長重複/公共子串」的標準重型武器。掌握 DA倍增構建 + Kasiski Height + RMQ 三部曲,可解決 POJ 2774、URAL 1517、HDU 3374 等經典題。注意分隔符選擇(必須小於所有字符且不出現在原串中)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved