AtCoder Beginner Contest 223 F,G問題メモ
F - Parenthesis Checking
問題
'(
' と')
' からなる文字列 S に対して、クエリが Q 個与えられる- クエリ
- oi,li,ri の3つの整数が与えられる
- oi=1 のとき、Sli と Sri の文字を入れ替える
- oi=2 のとき、SliSli+1...Sri からなる部分文字列が正しい括弧列か判定する
- クエリ2に全て答えよ
- 1≤|S|,Q≤2×105
解法
区間加算更新・区間min取得の遅延評価セグメント木。
遅延セグ木はACL Libraryにあるが、更新操作と取得操作の演算が違ってもちゃんと使えますか、という練習問題かも。
正しい括弧列の判定は、'(
' を +1、')
' を −1 に置きかえて、累積和を考えるのがよくある解法。
0 1 2 3 4 Sのindex -------------- S ( ( ) ) ( A 0 1 2 1 0 1 累積和 -------------- 0 1 2 3 4 5 累積和のindex
文字列 S から [l,r) を取り出した部分文字列が正しい括弧列かどうかは、以下をともに満たすかで判定できる。
- Al=Ar
- Al,Al+1,...,Ar の最小値が Al に等しい
要は、開き括弧と閉じ括弧の数は等しいですか、途中で開かれた括弧より閉じた括弧の方が多くなっていませんか、ということを確認している。
区間minが必要になるので、Aをセグメント木で管理するなどで、'2'のクエリには答えることができる。
しかし、今回は'1'のクエリ、括弧を入れ替えるという操作が出てくる。
Sl と Sr の入れ替えにより累積和がどう影響するかというと、
- Sl=Sr のとき
- 何も変わらない
- Sl=(、Sr=) のとき
- Al+1~Ar の値が −2 ずつされる
- Sl=)、Sr=( のとき
- Al+1~Ar の値が +2 ずつされる
このようになるので、区間加算ができればよい。
A に対して区間更新と区間取得ができればよいので、遅延評価セグメント木で実装できる。
抽象化された遅延セグ木は5つの関数を定義するが、今回は以下のようになる。
関数 | 役割 | 今回の問題 |
---|---|---|
e() | dataの単位元 | 常に ∞ を返す関数 |
id() | lazyの単位元 | 常に 0 を返す関数 |
op(a,b) | data同士のマージ | min |
mapping(f,x) | dataにlazyを反映 | add |
composition(f,g) | lazy同士のマージ | add |
上の記事からリンクされてるチートシートに、そのものズバリな設定がある。親切。
G - Vertex Deletion
問題
- N 頂点の木が与えられる
- この木の最大マッチングの大きさを m とする
- 以下の条件を満たす頂点 v の個数を求めよ
- v および v を端点に持つ辺を取り除いたグラフでも、最大マッチングの大きさは m のまま
- 2≤N≤2×105
解法
ABCで2回連続全方位木DPが出たので、世は空前の全方位DPブーム。
(先週せっかくライブラリ化したのに、今回Fで延々とバグらせ続けていたせいで日の目を見ませんでした。悲しいね)
グラフ上から、「どの2辺も両端の頂点を共有しない」という条件で選んだ辺集合をマッチングと呼ぶ。
その中で、取れる辺数が最大であるものを最大マッチングと呼ぶ。
通常のグラフにおける最大マッチングは難しいが、二部グラフや木では効率的に解ける方法がある。
木の最大マッチングはいろいろな解き方があるが、葉から貪欲にマッチングしていく、という方法が可能。
○ / \ ○ ○ /|\ /\ ○○○ ○○ | ○ ↓↓↓ ○ / \ ① ③ /|\ /\ ①○○ ③② | ②
上の例の場合、最大マッチングは3だが、 根の頂点自身はペアに使われずとも各子の部分木だけで完結しているので、 消しても最大マッチングに影響ない。
② / \ ① ② /|\ ①○○
逆にこの例では最大マッチング達成のためには根頂点が必要で、消すことが出来ない。
従って、根を決めて、以下のような木DPを考えた場合、
- DP[v]=(c,flag)
- 頂点 v 以下の部分木で達成できるマッチングが c 個
- 頂点 v 自身はそのマッチングに flag=0:使われている/1:使われていない
DP[根] の c が最大マッチングと一致し、flag=1 なら根は消せる。0 なら消せないとなる。
なお、根の c は最大になるに決まっているので、答えを求める上では flag だけ管理すればよいが、 イメージ的にわかりやすくするため c を添えて考える。
DP[v] の値は、v の子頂点たちを先に計算しておくことで、以下で求められる。
- flag=1 の子が1つでもある場合
- c は子頂点達の合計 +1(v と子で新規ペアが1個出来る)
- flag は '0'
- flag=1 の子が1つもない場合
- c は子頂点達の合計
- flag は '1'
- 葉は (0,1)
全方位DPを利用することで全頂点に対して「自身を根としたときの DP の値」が O(N) で求められる。