Official

C - Brackets Stack Query Editorial by Nyaan


この問題は良い括弧列(バランスの取れた括弧列)が持つ性質を上手く利用する問題です。
良い括弧列は次の性質を持ちます。

文字列 \(S\)( および ) からなる文字列であるとする。
この時、長さ \(|S|+1\) の数列 \(A=(A_0, A_1, \dots, A_{|S|})\) を次のように定義する。ここで \((1 \text{ if } S_i =\) ( \(\text{ else } -1)\) は 「\(S_i\)( である時 \(1\)、そうでない時 \(-1\)」という意味で用いている。

  • \(A_0 = 0\)
  • \(A_1 = A_0 + (1 \text{ if } S_1 =\) ( \(\text{ else } -1)\)
  • \(\vdots\)
  • \(A_i = A_{i-1} + (1 \text{ if } S_i =\) ( \(\text{ else } -1)\)
  • \(\vdots\)
  • \(A_{|S|} = A_{|S|-1} + (1 \text{ if } S_{|S|} =\) ( \(\text{ else } -1)\)

この時、\(S\) が良い括弧列である必要十分条件は次の通りである。

  • \(A_{|S|} = 0\) かつ \(\min\lbrace A_0, A_1, \dots, A_{|S|} \rbrace = 0\)

上記の性質がぱっとわからない方は、いくつかの括弧列 \(S\) に対して \(A\) を計算してみることで理解の助けになるのではないかと思います。(なお、証明は割愛します)

この上記の性質の \(A\) を stack 状のデータ構造で管理することで今回の問題を解くことが出来ます。
具体的には、今の文字列を \(S\) として、\(B_i = \min \lbrace A_0, A_1, \dots, A_i \rbrace\) と置いた時、stack に

  • \(A\) の値 \(A_0, A_1, \dots, A_{|S|}\)
  • \(B\) の値 \(B_0, B_1, \dots, B_{|S|}\)

を管理することにします。
まず、初期状態では \(S\) は空文字列なので \(A_0 = 0\)\(B_0 = 0\) だけが入っています。
1 番目のクエリで ( を追加するときは、新しい文字列の長さを \(n\) とした時

  • \(A_n = A_{n - 1} + 1\)
  • \(B_n = \min \lbrace A_0, A_1, \dots, A_n \rbrace = \min \lbrace B_{n-1}, A_n \rbrace\)

が成り立つので、直前の情報から新たな \(A_n, B_n\) を計算できます。( を追加する場合もほぼ同様です。
2 番目のクエリで \(S\) の末尾の文字を削除する場合は \(A, B\) の末尾の要素を削除すればよいです。
そして、今の文字列が良い括弧列かどうかの判定は \(A_{|S|} = 0\) かつ \(B_{|S|} = 0\) が成り立つかどうかで判定できます。

以上に説明するデータ構造を構成することで今回の問題を解くことが出来ます。計算量は \(\mathrm{O}(Q)\) で十分高速です。

  • 実装例(C++)
#include <iostream>
#include <vector>
using namespace std;

int main() {
  cin.tie(0)->sync_with_stdio(0);
  int Q;
  cin >> Q;
  vector<int> A{0}, B{0};
  for (int q = 1; q <= Q; q++) {
    int cmd;
    cin >> cmd;
    if (cmd == 1) {
      char c;
      cin >> c;
      A.push_back(A.back() + (c == '(' ? 1 : -1));
      B.push_back(min(B.back(), A.back()));
    } else {
      A.pop_back();
      B.pop_back();
    }
    cout << (A.back() == 0 and B.back() == 0 ? "Yes" : "No") << "\n";
  }
}

posted:
last update: