−目次
AtCoder Beginner Contest 244 G,Ex問題メモ
“Good Path” に関する問題がE,F,Gと3問続く、テーマ性のある回だった。
G - Construct Good Path
問題
- 頂点 辺の単純無向連結グラフが与えられる
- 0,1からなる長さ の文字列 が与えられる
- 以下のパスを1つ構築せよ
- 条件
- 長さ 以下
- 頂点 はパス中に、 なら偶数回、 なら奇数回出現する
- 0回も偶数回と見なす。また、長さ0のパスでもよい
解法
の頂点は訪れなくてもよいが、 の頂点は少なくとも1回は訪れる必要がある。
まずは(偶奇を無視していいので)'1'の頂点を全て訪れるパスの構築を考えたい。
最終的な答えは、そこから適当に往復なんかを加えれば調整できそう。
特定の全頂点を訪れるパス
グラフで「特定の全頂点を1回は訪れるパス」を考える場合、 順番に経路探索などしていては、その順番によっては非常に時間がかかるし、パスも冗長になる。
適当に根付き全域木を構築してその「オイラーツアー」を考えれば、全頂点を訪れられるし、パス長は必ず となる。
(ACの上では必要無いが)部分木に'1'がない頂点への訪れは不要なので、省略してもよい。
偶奇の調整
オイラーツアーで、一番最後にその頂点を訪れ、親に帰るときに調整を行う。
ⓟ
/ オイラーツアー(一部)
ⓥ .., p, v, 1, .., 1, v, 2, .., 2, v, 3, .., 3, v, p, ..
/|\ ~~~
①②③ ここ!
:::
この時点では、自身以外の、自身の部分木に含まれる頂点(①②③など)は、 もう訪れることはないので全て と整合しているはずである。していなければならない。
よってそこには触れられないとすれば、「親との行き来」によって調整するしかない、ということがはっきりする。
.., 3, .., 3, v,
~~~ もしこの時点で、v の出現回数がSvと合っていなければ
.., 3, .., 3, v, p, v,
親との往復を挟むと調整できる
もし仮に、この調整が全ての辺に対して必要になったとしても、1辺ごとに2頂点が追加されるだけなので、 「ベースのオイラーツアーで 」「調整で 回」で、 回以内に収まる。
唯一、親がない「根頂点」はどうするか、が問題となるが、その場合、もうパスは終了なので、末尾を取り除くことで調整できる。
Ex - Linear Maximization
問題
- 2次元座標上の点の集合 があり、はじめ、空である
- 個のクエリを処理せよ
- クエリ
- 整数 が与えられる
- 点 を に追加した後、 に含まれる点における の最大値を求めよ
解法
のとき、答えを与える点はその時点で にある点の の最大値または最小値。( の符号による)
それは簡単に管理できるので分けて考えるとして、以降、 とする。
例えばある に対する値を とおくと、よくある直線の式に変形すると となる。
- なら、 を通る傾き の直線の切片が大きいほどよい
- なら、 を通る傾き の直線の切片が小さいほどよい
これは、 なら、傾き の直線を無限の上空からぐい~んと降ろしてきて、 最初に引っかかった頂点が最大値を与える、ということになる。
~~--__ ↓↓↓
~~--__
~~--__ => ~~--__ こいつ
・ ・ ~~--__ ↓
・ ・ ・ ~・-__
・ ・ ・ ・
なら逆に、下から上がってくる直線をイメージする。
点がいっぱい集まってくると、大抵は「どんな傾きであろうと、自身より上の点に邪魔されて絶対に引っかからない点」というのができてくる。
引っかかりうる点の候補は、つまり「凸包」と同じである。
点の追加
最初に点が全て与えられる凸包は、Andrew's Monotone Chain という方法によって構築できるが、今回はそれを応用する。
の場合は(直線を上から降ろすので)上側凸包を使うが、 上側凸包は、 座標順に見ると必ず進行方向が時計回りになっている。
追加することによってそれが崩れないかをチェックしていけばよい。
ソート状態を保った上で途中に追加・削除を行いたいので、平衡二分探索木や、SortedSetなどのデータ構造を用いる。
上側凸包に点 を追加したいとき、
- 自身が上側凸包に追加され得るか?
- 既に 座標が である他の点 が凸包に含まれているとき、
- なら、凸包には含まれ得ない
- なら、凸包が更新されるため、続ける(既存の点は削除しておく)
- のすぐ左の点 を取得する
- のすぐ右の点 を取得する
- が時計回りなら含まれる(データ構造に を追加する)
- を追加したことにより、不要になる点がないか?
- 左側
- のすぐ左の点 を取得する(*)
- のすぐ左の点 を取得する
- が時計回りか判定する
- 時計回りなら、終了
- 時計回りでないなら、 を削除し、(*)に戻る
- の左側に点が1個しかなくなったら終了
- 右側も同様
こうすることにより、上側凸包を更新できる。下側も同様。
クエリへの回答
点の追加と同様、 の正負によって上側凸包か下側凸包かを使い分けるが、ここでは として上側を考える。
上側凸包上で、傾き を下ろしたときに最初に引っかかる点を求めたい。
ここで、凸包を構成する線分を考えると、最初に引っかかる点というのは、 凸包を構成する線分の中で傾きが に最も近い線分の一端であるはずである。
上側凸包では、 座標順に見ると、線分の傾きは、大→小へと、単調減少する。
つまり、二分探索が可能である。
「 以下の、最大の傾きを与える線分」を探索し、その両端のいずれかが最大値を与える点となる。
実装
- 点の追加では、 座標をキーに挿入位置を求める
- クエリの回答では、傾きをキーに答えとなる線分を求める
「 座標」と「傾き」のそれぞれをキーとして使いたい場面が出てくる。
ただ、一般的なライブラリにある平行二分探索木では、整列・探索に用いるキーは1種類しか指定できないのが普通。
(というか、たまたま今回はこの2つのソート結果が同じになるという特殊な性質があっただけ)
まぁ、キーを変えられる特殊なデータ構造を実装してもよいのだが、 いずれか片方をキーとしてライブラリを使って、 もう片方を求める際には、自前の二分探索で求めるのが手っ取り早いだろう。(多少、遅くなるが)
小数が現れないこと、また求めたい操作などを考えると、今回は 座標をキーにするのがよさそう。

