Official

I - ABCBA Editorial by physics0523


まず、 \(S\)\(S\) を反転させた文字列をこの順に連結した文字列は明らかに回文なので、正解となる文字列の長さが \(2 \times |S|\) 以下であることが分かります。

\(S\) を接頭辞に持つ長さ \(|S|+k\) ( \(0 \le k \le |S|\) ) の回文がどのようなものか考えてみましょう。
できた回文を \(S_1S_2 \dots S_{|S|}T_1T_2 \dots T_k\) とします。このとき、回文の定義より以下の等式を得ます。

  • \(S_1 = T_k\)
  • \(S_2 = T_{k-1}\)
  • \(\dots\)
  • \(S_k = T_1\)
  • \(S_{k+1} = S_{|S|}\)
  • \(\dots\)
  • \(S_{ \lfloor (|S|+k+1)/2 \rfloor}=S_{\lfloor (|S|+k+2)/2 \rfloor}\)

これを解釈すると、「 \(S\) の末尾 \(|S|-k\) 文字が回文をなす」となります。また、 \(T_1,T_2,\dots,T_k\)\(S\) の内容から一意に定まることも分かります。
つまり、解くべき問題は次のものになりました。

\(S\) の接尾辞であって、最長の回文を求めよ。

この問題には種々の解法があります。そのうち最も直接的なものは Manacherのアルゴリズム を活用する方針でしょう。

あとは、先ほどの問題を解いた結果から元の問題の答えを復元すればよいです。 Manacher のアルゴリズムを利用すると、時間計算量は \(O(N)\) となります。

実装例 (C++):

#include<bits/stdc++.h>

using namespace std;

vector<int> manacher(string &S){
  vector<int> R(S.size());
  int i = 0, j = 0;
  while (i < S.size()) {
    while (i-j >= 0 && i+j < S.size() && S[i-j] == S[i+j]) ++j;
    R[i] = j;
    int k = 1;
    while (i-k >= 0 && k+R[i-k] < j) R[i+k] = R[i-k], ++k;
    i += k; j -= k;
  }
  return R;
}

int main(){
  string s;
  cin >> s;
  string ms="$";
  for(auto &nx : s){
    ms.push_back(nx);
    ms.push_back('$');
  }
  auto r=manacher(ms);
  int k=0;
  for(int i=s.size();;i++){
    if(r[i]>=s.size()-k){break;}
    k++;
  }

  cout << s;
  for(int i=0;i<k;i++){
    cout << s[k-1-i];
  }cout << "\n";
  return 0;
}

posted:
last update: