【AtCoder】ABC 428 C - Brackets Stack Query

実行時間制限: 3 sec / メモリ制限: 1024 MiB / Difficulty: 483 / NoviSteps: 2Q / 配点: 300 点

問題概要

文字列 TT が次の条件を満たすとき、TT を良い括弧列と呼ぶ。

  • 次の操作を 00 回以上繰り返すことで TT を空文字列にすることができる。
    • TT に連続部分文字列として含まれる () を選び、これを取り除く。

文字列 SS があり、 SS ははじめ空文字列である。 以下で説明される 2 種類のクエリを与えられる順に QQ 個処理し、各クエリの直後に SS が良い括弧列であるかを判定せよ。

  • 1 c : 文字 cc が与えられる。cc( または ) である。ccSS の末尾に追加する。
  • 2 : SS の末尾の文字を削除する。この時、SS は空文字列でないことが保証される。

制約

  • 1Q8×1051 \leq Q \leq 8 \times 10^5
  • QQ は整数

考察

競プロで括弧列と言えば、stackあるいは (, )±1\pm 1 に対応させた数列を扱うのが定石である。

「良い括弧列」の定義の言い換え

まずは、次の 2 つの数列 T,AT, A を考える(1iN)(1 \leq i \leq N)

Ti={1(Si=’(’)1(Si=’)’)Ai=j=1iTj(A0=0)\begin{align*} T_i & = \begin{cases} 1 & (S_i = \texttt{'('}) \\ -1 & (S_i = \texttt{')'}) \\ \end{cases} \\ A_i & = \sum_{j=1}^{i} T_j \quad (A_0 = 0) \end{align*}

すなわち、TTSS の各文字に対応する数列、AATT の累積和である。

ここで、今回の「良い括弧列」の定義は以下のように言い換えられる。

  • SS に含まれる( の数と ) の数は等しい。
  • 任意の 1kS1 \leq k \leq |S| に対して、 (Sの先頭からk文字目までの’(’の個数)(Sの先頭からk文字目までの’)’の個数) \begin{equation*} (S の先頭から k 文字目までの\texttt{'('}の個数) \geq (S の先頭から k 文字目までの\texttt{')'}の個数) \end{equation*}

これを、条件 (☆) と呼ぶことにする。

2 つ目の条件は、例えば)(())(のような文字列が良い括弧列でないことを考えれば分かりやすいだろう。

条件 (☆) は前述の T,AT, A を用いて表すと、

  • AS=0A_{|S|} = 0
  • 任意の 1kS1 \leq k \leq |S| に対して、 Ak0A_k \geq 0

となるから、各クエリの直後に条件 (☆) を満たすかを判定すればよい。


...普通はこれで良いのだが、今回は QQ が最大で 8×1058 \times 10^5 と大きいため、各クエリの直後に AA の全要素を調べるのでは、判定のための計算量が O(Q)O(Q) となってしまい、間に合わない。

そこで、以下で定義される AA の累積 min 数列 BB を導入する。

Bi=min1jiAj(B0=0)B_i = \min_{1 \leq j \leq i} A_j \quad (B_0 = 0)

すると、条件 (☆) はさらに以下のように言い換えられる。

  • AS=0A_{|S|} = 0
  • BS0B_{|S|} \geq 0

こうすることで、各クエリの直後に ASA_{|S|}BSB_{|S|} の 2 つを調べるだけでよくなり、この部分の計算量が O(1)O(1) になる。

クエリの処理

一方、各クエリは次のように処理できる。 ここで、数列は全てstackで実装する。

  • 1 c : cc に対応する値を tt とし、AA の末尾に AS+tA_{|S|} + t を追加する。その後、BB の末尾に min(BS,AS)\min(B_{|S|}, A_{|S|}) を追加する。
  • 2 : AABB の末尾をそれぞれ削除する。

実装例も参考のこと。


これらの処理は全て計算量 O(1)O(1) で行えるため、全体の計算量は O(Q)O(Q) となる。

これは、本問の制約下で十分に高速である。

実装例

CPP
1.#include <bits/stdc++.h>
2.using namespace std;
3.
4.// ======================================== //
5.
6.int main()
7.{
8. int Q;
9. cin >> Q;
10.
11. stack<int> A, B;
12. A.push(0);
13. B.push(0);
14.
15. while (Q--)
16. {
17. int t;
18. cin >> t;
19.
20. if (t == 1)
21. {
22. char c;
23. cin >> c;
24.
25. A.push(A.top() + (c == '(' ? 1 : -1));
26. B.push(min(B.top(), A.top()));
27. }
28. else if (t == 2)
29. {
30. A.pop();
31. B.pop();
32. }
33.
34. if (A.top() == 0 && B.top() >= 0)
35. cout << "Yes" << endl;
36. else
37. cout << "No" << endl;
38. }
39.
40. return 0;
41.}
atcoder.jp favicon

実装時間: 10 分

コメント

括弧列に関しては以下の記事が詳しいので参考のこと。

【競プロ】括弧列の性質・知識をまとめてみる - Qiitaqiita.com favicon