ARMERIA

Rubyと競技プログラミングの話 AtCoderやCodeforcesの問題解説記事が多め。

ARC101 参加記録

水色以上の人にとっては実に1ヶ月以上ぶりのratedコンテストとなりました。そのぶん気合十分、緊張も十分の状態で臨みましたが…

結果は74分台2完で139位。700点問題を解くことができて、レートも結構上がりました。

f:id:betrue12:20180826004211p:plain

CとDを振り返っていきます。

C - Candles

C - Candles

解法

ろうそくの消し方は、初期位置(座標0)とろうそくの位置関係によって以下の3パターンに分かれます。

  • ろうそくが全部左(0以下)にある。一番左のろうそくまでの片道距離がかかる。
  • ろうそくが全部右(0以上)にある。一番右のろうそくまでの片道距離がかかる。
  • ろうそくが左右どちらにもある。途中でUターンが必要であり、左右端のうち短いほうへの往復距離と長いほうへの片道距離がかかる。

これらどのパターンにおいても、「連続しないろうそくをわざわざ選ぶ」ことで得になることはないです。イメージとしては、できるだけ密集しているろうそくを選んだほうが得な気がしますよね。

 N 個のろうそくから連続する  K 個のろうそくを選ぶ選び方は  N - K + 1 通り。距離計算に使うのは両端だけなので、1パターンごとの計算は  O(1) でできます。全パターン探索しても全体計算量は  O(N) N \le 10^{5} なので間に合います。

ACコード

D - Median of Medians

D - Median of Medians

恐怖の700点問題。これはABCに出すには流石に無理がありますね…ただ、知っておいて損はない問題だとは思います。

長さ  N の数列の連続部分列のとり方は  \frac{N(N+1)}{2} 通りありますが、それら全ての中央値を集めて、さらにその中央値を求める問題。要素数がとても多くなるので、直接求めるのは難しそうです。ちょっと細かく考察を書いていきます。

中央値について考察

「中央値」というのは、要素の個数に直接関係する概念です。例えば

  •  1, 2, 3, 4, 5, 6, 7

という数列の中央値は4ですが、これは「4以上の要素が半数以上存在する」ということを意味します。

ただ、もちろん1や2についても同様のことが言えますね。「  X 以上の要素が半数以上存在する」を満たす数はたくさんありますが、そのうち最も大きな  X が、(この問題上の定義における)中央値です。これは例えば

  •  1, 4, 4, 4, 4, 4, 5

のように分布の偏った数列についても同じことが言えます。

平均値と違い、中央値を考える上では「中央値以上かどうか」だけが重要です。

  •  1, 2, 3, 4, 5, 6, 7 でも、
  •  1, 2, 3, 4, 5, 6, 5000兆 でも、
  •  1, 2, 3, 4, 5000兆, 5000兆, 5000兆 でも、

どれも中央値は4です。

Yes/No問題+二分探索への転換

前項で書いたとおり、「中央値は  X 以下である」ことと、「  X 以上の要素が半数以上存在する」ことは同値です。

そして、「整数  X が与えられたとき、集合の要素の中に  X 以上の要素は半数以上存在するか?」というYes/No問題は、直接中央値を求める問題よりもずっと考えやすくなります。というのも、各要素の具体的な値は全部無視して、  X 以上かどうか、だけで2値化して考えれば良いからです。

このことから、中央値の二分探索を行うという方針が立ちそうです。具体的には、二分探索の各ステップごとに  X を固定して、先ほどのYes/No問題を解き、その結果に応じて範囲を狭めて求める中央値を特定していく…という方法を考えます。*1

区間数え上げの効率化

さて、元の問題は「 \frac{N(N+1)}{2} 個の連続部分列の中央値  \frac{N(N+1)}{2} 個を集めた集合の中央値を求めよ」でした。

Yes/No問題に切り替えた後は、  \frac{N(N+1)}{2} 個の区間のうち、区間内の要素の中央値が  X 以上となるような区間の数を数えるのが次の目標になります。そのような区間は、やはり中央値の性質から「区間内に  X 以上の要素が半数以上あるような区間」と言い換えることができます。

これを全ての区間について直接計算すると結局  O(N^{2}) 以上の時間がかかって間に合わないので、効率化のため累積和を使います。 X 以上の要素を+1、 X 未満の要素を-1として累積和を取った時、区間和が0以上であるものが「区間内に  X 以上の要素が半数以上あるような区間」です。

このように累積和を用いて、区間和がある条件を満たす区間を数え上げる問題はお馴染みです。ただ例えば「区間和がぴったり0」などの条件でしたら*2単純にmapなどで個数管理すればよいのですが、今回は「以上」なので範囲和が必要になります。そのため、BIT(Binary Indexed Tree)などを使うことになります。

f:id:betrue12:20180826115439p:plain

BITについては、蟻本の解説、もしくは以下のスライドがオススメです。

http://hos.ac/slides/20140319_bit.pdf

解法まとめ

これで問題を解く全ての材料が揃いました。手順としては、

  1. 二分探索を試みる。各ステップでは  X を固定し、「 \frac{N(N+1)}{2} 個の中央値の中に  X 以上のものは半数以上存在するか?」のYes/No問題を考える。
  2. 与えられた数列の、  X 以上の要素を+1、  X 未満の要素を-1として累積和を取り、区間和が0以上になる区間をBITを利用して数え上げる。この合計が、 \frac{N(N+1)}{2} 個の中央値の中に存在する  X 以上の要素数となる。
  3. 数え上げ結果によってYes/No問題が解かれるので、これを繰り返して二分探索の範囲を狭め、解となる中央値を特定する。

となります。

何と言ってもABCに出たので凶悪難易度に見えますが、700点問題として見ると適正なのかな…と思います。

ACコード

実装上の細かい話ですが、BITは普通は負のインデックスに値を格納できないので、適当にオフセットを付けてやる必要があります。上のコードは0-indexedのBITなので +N してますが、1-indexedのBITだと0を格納しようとしてバグる危険性がちょっとだけあるので、不安なら長さを多めにとって +N+1 しましょう。

また、二分探索は制約範囲内の整数全部を考えても良いのですが、「元の数列に存在する要素のどれかしか中央値になり得ない」ということから、元の数列の要素をソートして候補として使っても良いです。上のコードはそうしています。反復回数がちょっと少なくなると思います。

さいごに

過去にABCで出た700点問題は「考察キツいけど数学で解ける」だったり「考察キツいけどDijkstraで解ける」だったりしたのですが、今回は考察もキツめでBITも必要だったことを考えるとかなり厳しかったのではないかと思います。ARCでもこれが解ければ最遅でも黃パフォだったので、そのくらいのレベルです。

私はこのくらいの難易度の問題を安定させられるようになりつつ、全く歯が立たなかった900点問題に少しでも手がかかるようにしたいな…と思います。ちょっと黄色が見えてきた気がするので、引き続き頑張ります。

脚注

*1:「中央値は二分探索と相性が良い」というのは、競プロ的には知識として持っておくと役立つと思います。ただその理由である、「中央値がX以下であるか」問題に転換すると要素を2値化できるので解きやすい、というところまで合わせて理解しておくほうがいいと思います。

*2:有名なZero-Sum Rangesなど