Official

F - Transpose Editorial by physics0523


一度に全ての操作を考えると混乱のもとなので、手の付けやすい複数の操作に分解することもひとつの手です。

まず、最終的な文字列の大文字小文字について判定しましょう。
これはそれほど難しくなく、以下のように判定できます。

  • 括弧列の深さが 偶数 であるところに位置する文字は、大文字小文字が 反転しない。
  • 括弧列の深さが 奇数 であるところに位置する文字は、大文字小文字が 反転する。

これに従って予め文字の大小を入れ替えておくことで、これ以上文字の大小を考える必要はなくなりました。

いま、考えるべきは次のような操作です。

  • 先頭と末尾がそれぞれ () であるような連続部分列について、 () とを取り除いたうえで文字列を反転させる

最終的な文字列は以下のような再帰で求めることが可能です。

  • \(f(l,r,d)\) という再帰関数を定義する。
  • \(d = 0\) のとき、 \(l, l+1,\dots,r\) 文字目の順に調べ、英文字が出てきたらその都度出力する。
    • その途中 \(l_u\) 文字目に ( を発見した時、それに対応する )\(r_u\) 文字目とする。
    • このとき、 \(f(l_u+1,r_u-1,1)\) を呼び、その後 \(r_u+1\) 文字目から処理を続行する。 ( \(r_u+1\) 文字目以降が出力されるのは \(l_u\) 文字目から \(r_u\) 文字目までの処理が終わった後であることに注意せよ。)
  • \(d = 1\) のとき、 \(r,r-1,\dots,l\) 文字目の順に調べ、英文字が出てきたらその都度出力する。
    • その途中 \(r_u\) 文字目に ) を発見した時、それに対応する (\(l_u\) 文字目とする。
    • このとき、 \(f(l_u+1,r_u-1,0)\) を呼び、その後 \(l_u-1\) 文字目から処理を続行する。

対応する () は予計算により時間計算量 \(O( |S| )\) で求めることができます。
あとは、 \(f(1,|S|,0)\) を呼び出すことで、この問題に時間計算量 \(O(|S|)\) で正解できます。

実装例 (C++) :

#include<bits/stdc++.h>

using namespace std;

char flp(char c){
  if('A'<=c && c<='Z'){
    return (c-'A'+'a');
  }
  return (c-'a'+'A');
}

string s;
vector<int> mch;

void f(int l,int r,int d){
  if(d==0){
    while(l<=r){
      if(s[l]=='('){
        f(l+1,mch[l]-1,1);
        l=mch[l];
      }
      else{
        cout << s[l];
      }
      l++;
    }
  }
  else{
    while(l<=r){
      if(s[r]==')'){
        f(mch[r]+1,r-1,0);
        r=mch[r];
      }
      else{
        cout << s[r];
      }
      r--;
    }
  }
}

int main(){
  cin >> s;
  int l=s.size();
  mch.resize(l);
  for(auto &nx : mch){nx=-1;}

  int h=0;
  vector<int> stk;
  for(int i=0;i<l;i++){
    if(s[i]=='('){
      stk.push_back(i);
      h++;
    }
    else if(s[i]==')'){
      mch[i]=stk.back();
      mch[stk.back()]=i;
      stk.pop_back();
      h--;
    }
    else if(h%2){
      s[i]=flp(s[i]);
    }
  }

  f(0,l-1,0);
  return 0;
}

posted:
last update: