ABC276 F - Double Chance を解くときに考えたこと
はじめに
自分の思考を殴り書いているような面があるので、文章の体裁はあまり保たれていませんし、解説もそこまでわかりやすくないです。
また、厳密性を欠くような部分もあると思いますが、ご容赦ください。
問題概要
整数列 が与えられます。
について、以下のクエリに答えてください。
の部分列 から無作為に つの要素を選ぶことを二回繰り返します。(このとき、要素が重複してもよいです)
選ばれた要素のうち最大のものの期待値を有理数 で出力してください。
とっかかり
とりあえずあるに対する期待値がいくらか考えてみます。
がソートされていると嬉しいので、例えば std::multiset などに入れ、ソートされた を考えたいです。
混同を防ぐためにソートされた を と書くことにします。
最初に が選ばれたとき、それが最後まで最大である場合の数を考えてみましょう。
これは二回目で 以下の値を引けばいいですから、 を超えない最大のインデックスを として です。
逆に、二回目で が選ばれたとき、それが最大である場合の数も、 です。
よって、 が最大である場合の数は であるように思えますが、罠があります。
それは を二回選ぶような方法を重複してカウントしていることです。
よってその分を引いて場合の数は であると分かりました。
したがって求めるべき期待値は です。
これを便宜上、 と書くことにします。
計算量改善
さて、これを愚直に計算すると、全体計算量になってしまい間に合いません。(ユーザー解説にある通りコンパイル設定をいじれば通せはします)
そこで計算結果を使いまわすことを考えます。
が、ソートした結果 に入ったとして、期待値を計算しましょう。
の期待値に対する寄与を考えると、これは が求められれば良いです。
例えば std::multiset::upper_bound 等で 時間でできます。
次に、 以外の寄与を考えます。 であるとき、 であり、 のとき、です。
したがって 以外の寄与は
と計算できます。
は使いまわせますから、 を素早く計算することを考えます。
区間和なのでセグ木を使いたいな~という気持ちになります。
そこで、重複している場合にインデックスを増やす座圧を行ったうえで、全要素 で初期化した長さ のセグ木を作りそこに要素を入れていけばよいです。
こうすると が で求められました。
したがって一クエリあたりの計算量は であり、総計算量 でこの問題に答えることができました。これは十分高速に動作し、 AC を得ることができます。