Manacher

Manacher computes all palindrome radii in a string in linear time.

After preprocessing, palindrome checks for arbitrary substrings can be answered in O(1).

Use cases

Typical use cases include:

Idea

A naive approach expands around every center and can take O(n^2) time.

Manacher reuses an already known palindrome interval [l, r].
If the current position i is inside that interval, the radius of the mirrored position l + r - i gives a lower bound.
Then it only compares characters outside the already known region.

Each character is newly compared only a constant number of times in total, so the whole algorithm runs in O(n).

Odd and even radii

This module stores odd-length and even-length palindromes separately.

odd[i] = r

means that the odd palindrome centered at i

s[i-r+1 .. i+r-1]

exists. Its length is 2*r - 1.

even[i] = r

means that the even palindrome centered between i-1 and i

s[i-r .. i+r-1]

exists. Its length is 2*r.

Import

import atcoder/extra/string/manacher

Type

type Manacher[T]

For string, Manacher[char] is constructed.
It also works for general sequences such as seq[int].

Constructors

proc initManacher[T](s: openArray[T]): Manacher[T]
proc initManacher(s: string): Manacher[char]

Radius helpers

proc manacherOdd[T](s: openArray[T]): seq[int]
proc manacherOdd(s: string): seq[int]

proc manacherEven[T](s: openArray[T]): seq[int]
proc manacherEven(s: string): seq[int]

proc palindromeRadii[T](s: openArray[T]): tuple[odd, even: seq[int]]
proc palindromeRadii(s: string): tuple[odd, even: seq[int]]

Operations

proc len[T](m: Manacher[T]): int

proc isPalindrome[T](m: Manacher[T], l, r: int): bool
proc longestPalindromeRange[T](m: Manacher[T]): tuple[l, r: int]
proc longestPalindromeLength[T](m: Manacher[T]): int

proc countPalindromicSubstrings[T](m: Manacher[T]): int
proc countPalindromes[T](m: Manacher[T]): int

Example

let m = initManacher("ababa")doAssert m.odd == @[1, 2, 3, 2, 1]doAssert m.even == @[0, 0, 0, 0, 0]doAssert m.isPalindrome(0, 5)doAssert m.isPalindrome(1, 4)doAssert not m.isPalindrome(0, 4)doAssert m.longestPalindromeLength == 5doAssert m.longestPalindromeRange == (l: 0, r: 5)doAssert m.countPalindromicSubstrings == 9

Even-length palindromes are also supported.

let m = initManacher("abba")doAssert m.odd == @[1, 1, 1, 1]doAssert m.even == @[0, 0, 2, 0]doAssert m.isPalindrome(0, 4)doAssert m.longestPalindromeLength == 4

Nim-friendly usage

Manacher works not only for strings but also for comparable sequences.

let m = initManacher(@[1, 2, 1, 2, 1])doAssert m.isPalindrome(0, 5)doAssert m.longestPalindromeLength == 5

UFCS makes queries readable.

if m.isPalindrome(l, r):  echo "palindrome"

Complexity

For string length n:

Notes

Practice problems

Show problems that can use this library (may reveal the intended technique)