公式

F - A/AB Insertion 解説 by physics0523 印刷ページ


原案: physics0523

A を \(+1\) 、 B を \(-1\) に置き換えて考えます。

AB, A の挿入を利用しても、累積和が負になるような地点は作れないことが分かります。
逆に、累積和が負になる地点が無ければ必ず(置き換える前の)文字列を作ることができます。
証明:
文字列から B が無くなるまで AB を除去することを考えます。
累積和が負になる地点が無いという仮定より、文字列中の先頭の B の直前の時点で累積和は必ず正です。このことから、その B の直前に必ず A が存在することが分かります。
その AB を除去する前と後で、除去に関与しない部分の累積和は不変です (取り除く部分の総和が \(0\) であるため)。なので、この除去を行っても累積和が負になる地点が無いという状態は変わりません。
これを繰り返し、全ての文字が A である文字列を得られ、あとはそれらを個別に除去することを考えます。
最後に、全ての時系列順を逆転させ、除去を挿入に言い換えることで元の文字列が確かに作れることが確認できます。

あとは、部分文字列に対して累積和が負になる地点があるかどうかを判定できればよいです。
ひとつの方法として、遅延伝搬 segment tree (Lazy Segtree) を利用することでこの判定を行うことができます。具体的には、各点の累積和を維持するために区間加算・区間最小値取得ができればよいです。

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

実装例 (C++):

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

using namespace std;
using namespace atcoder;

using S=int;
using F=int;

S op(S l,S r){return min(l,r);}
S e(){return 1e9;}
S mapping(F l,S r){return (l+r);}
F composition(F l,F r){return (l+r);}
F id(){return 0;}

int main(){
  ios::sync_with_stdio(false);
  cin.tie(nullptr);
  int n;
  string s;
  cin >> n >> s;
  vector<S> ini(n+1,0);
  for(int i=0;i<n;i++){
    if(s[i]=='A'){ini[i+1]=ini[i]+1;}
    else{ini[i+1]=ini[i]-1;}
  }
  lazy_segtree<S,op,e,F,mapping,composition,id> seg(ini);
  int q;
  cin >> q;
  while(q--){
    int typ;
    cin >> typ;
    if(typ==1){
      int i;
      string c;
      cin >> i >> c;
      i--;
      int del;
      if(s[i]=='A'){del=-1;}
      else{del=1;}
      s[i]=c[0];
      if(s[i]=='A'){del++;}
      else{del--;}
      seg.apply(i+1,n+1,del);
    }
    else{
      int l,r;
      cin >> l >> r;
      l--;
      if(seg.prod(l,r+1)==seg.get(l)){cout << "Yes\n";}
      else{cout << "No\n";}
    }
  }
  return 0;
}

投稿日時:
最終更新: