Manacher による最大長回文探索アルゴリズム

参考

概要

文字列 \(s\) が与えられた時、\(s\) の各点を中心とする回文の最大長を求める. ここで \(n = |s|\) とすると、各点とは

  • \(i = 1,2, \ldots, n\) 及び
  • \(j = (1, 2), (2, 3), \ldots, (n-1, n)\)

の二種類のこと. \(i\) は各文字に対応し、\(j\) は隣り合った2文字の間を指す. これを中心とする (連続) 部分文字列とは

  • 中心 \(i\) に就いて \(s(i - x, i + x)\) または
  • 中心 \(j = (j_0, j_1)\) に就いて \(s(j_0 - x, j_1 + x)\)

のこと.

入出力

返り値は \[1, (1, 2), 2, (2, 3), 3, \ldots, n-1, (n-1, n)\] を中心としたする回文の最大長を収めたベクトル.

/// String - Test Palindric - Manacher Algorithm
pub fn manacher<A: Ord>(s: &Vec<A>) -> Vec<usize> {
    let n = s.len();
    let mut r = vec![0; 2 * n - 1];
    let mut i = 0;
    let mut j = 0;
    while i < 2 * n - 1 {
        while i >= j && i + j + 1 < 2 * n && s[(i - j) / 2] == s[(i + j + 1) / 2] {
            j += 1;
        }
        r[i] = j;
        let mut k = 1;
        while i + k < 2 * n - 1 && i >= k && r[i] >= k && r[i - k] != r[i] - k {
            r[i + k] = std::cmp::min(r[i - k], r[i] - k);
            k += 1;
        }
        i += k;
        j = if j > k { j - k } else { 0 }
    }
    r
}

#[cfg(test)]
mod test_manacher {
    use crate::string::manacher::*;

    #[test]
    fn it_works() {
        assert_eq!(manacher(&"a".chars().collect()), vec![1]);
        assert_eq!(manacher(&"ab".chars().collect()), vec![1, 0, 1]);
        assert_eq!(manacher(&"aba".chars().collect()), vec![1, 0, 3, 0, 1]);
        assert_eq!(manacher(&"abc".chars().collect()), vec![1, 0, 1, 0, 1]);
    }
}