Official

E - リピート再生と部分一致 / Repeat Playback and Partial Matching Editorial by physics0523


メモ: この問題は AWC-E として発展的な難易度であると思われます。本解説の筆者はこの問題を ABC-FG 間程度の難易度であると考えています。


問題文の指示通り、 \(R-L+1 > N\) なるケースは No として除去して考えます。

以降、 \(L,R\) と添え字から \(1\) を引き、 0-indexed であるとして話を勧めます。
この問題では、 \(S^{\infty}[L,R]\)\(S\) の連続部分列であるかどうかを判定する問題です。
例えば \(S=\) abcabc のようなケースを想像すると、この判定はそう自明ではないことがわかります。
また、着目すべき連続部分列が \(O(N^2)\) 通りあり、これら全てに対する愚直な予計算等もできないことがわかります。(この着想をを発展させると、 suffix automation を利用した解法に行きつきます。)
では、どうすればよいでしょうか?

\(S^{\infty}\) のような文字列に対して、 \(S\) の有限回の連結に帰着させて議論するというのはひとつの典型です。今回は、 \(S+S\) ( \(S\)\(2\) 回連結 ) に帰着させることが筋が良い方針です。

文字列 \(ss=S+S\) に対して Suffix Array / LCP Array を議論します。
クエリの文字列は必ず \(ss\)\(i\) (\(0 \le i < N\)) 文字目から始まる部分の ( 長さ \(N\) 以下の ) 接頭辞であり、着目すべき \(S\) の連続部分列は \(ss\)\(i\) (\(N \le i < 2N\)) 文字目から始まる部分の接頭辞です。
\(ss\)\(i\) (\(0 \le i < N\)) 文字目から始まる部分の接頭辞のうち長さ \(k\) 以下のものに対しては Yes、そうでないものに対しては No と答えるべきという閾値 \(k\) が存在することがわかります。
この閾値を SA/LCP でうまくまとめて求められないでしょうか?

着目すべき \(S\) の連続部分列に対応する要素 ( すなわち、 \(ss\)\(i\) (\(N \le i < 2N\)) 文字目から始まる部分の接頭辞 ) に対しては閾値が直ちにわかります。
その閾値の降順に管理する priority_queue を持った上で、その priority_queue 上で順に伝播を行うことを考えます。
SA の性質から、閾値の伝搬は SA 上で隣接する要素にしか行わなくてよいことがわかります。伝播すべき値は現在地の閾値と隣接部分の LCP の値との min です。
この伝播により、 \(S^{\infty}\) の部分列 \(N\) パターン全てについて閾値を求めることができました。

時間計算量は \(O(Q + N \log N)\) です。

実装例 (C++):

#include<bits/stdc++.h>
#include<atcoder/string>

using namespace std;
using namespace atcoder;

using ll=long long;
using pl=pair<ll,ll>;

int main(){
  ll N,Q;
  cin >> N >> Q;
  string S;
  cin >> S;
  string ss=S+S;
  auto sa=suffix_array(ss);
  auto lcp=lcp_array(ss,sa);
  vector<ll> inv(2*N);
  for(ll i=0;i<2*N;i++){
    inv[sa[i]]=i;
  }
  vector<ll> exi(2*N,-1);
  priority_queue<pl> pq;
  for(ll i=N;i<2*N;i++){
    exi[inv[i]]=2*N-i;
    pq.push({exi[inv[i]],inv[i]});
  }
  while(!pq.empty()){
    auto od=pq.top(); pq.pop();
    if(exi[od.second]!=od.first){continue;}
    if(od.second>0){
      ll tg=od.second-1;
      ll ne=min(od.first,(ll)lcp[tg]);
      if(exi[tg]<ne){
        exi[tg]=ne;
        pq.push({exi[tg],tg});
      }
    }
    if(od.second+1<2*N){
      ll tg=od.second+1;
      ll ne=min(od.first,(ll)lcp[od.second]);
      if(exi[tg]<ne){
        exi[tg]=ne;
        pq.push({exi[tg],tg});
      }
    }
  }

  while(Q--){
    ll L,R;
    cin >> L >> R;
    L--; R--;
    ll len=R-L+1;
    if(len > N){cout << "No\n"; continue;}
    ll bas=L%N;
    if(exi[inv[bas]]>=len){cout << "Yes\n";}
    else{cout << "No\n";}
  }
  return 0;
}

posted:
last update: