E – Best Performances

問題の趣旨は以下のとおりです。

配列 A のある要素の値を別の値に変更する。
そのあと降順にソートして先頭から K 番目までの総和を求めよ。
配列 A の長さは最大 5 × 10^5 であり、この処理も最大 5 × 10^5 回繰り返される。

実際に何度もソートしていては時間がかかりすぎます。時間短縮する方法はないのでしょうか?

優先度付きキューで解決

まず 配列 A の要素を大きい順に並べたときに K 番目までになるもの(Larges)とそうでないもの(Smalls)にわけます。値が更新されたらLargesの最小値とSmallsの最大値を比較します。もしSmallsの最大値のほうがLargesの最小値よりも大きい場合は所属するグループを入れ替えます。Larges の最小値とSmallsの最大値は優先度付きキューを使えばソートしなくても短時間で取得できます。

優先度付きキューはPriorityが最小のものしか取り出すことができません。また配列の要素の値を更新してしまうとPriorityも変更しなければなりませんが、普通の方法ではできません。そこで配列の要素の値が更新されたら優先度付きキュー内にある対応する要素は無効にして、新しい要素をそのつど追加していくことにします。

Solverクラスの定義

配列の要素と優先度付きキュー内に格納されている要素を関連付けるために以下のクラスと変数を定義します。

コンストラクタ

コンストラクタにおける処理を示します。

配列の最初の K 個は 何も考えずに Larges に格納します。それ以降は Larges の最小値と比較して条件分岐させます。追加しようとしている要素が Larges の最小値より大きい場合は Larges の最小値を Larges から削除して Smalls へ移動させ、新しく追加する要素を Larges に追加します。そうでない場合は Smalls へ追加します。

上位グループと下位グループの入れ替え

ToSmallsメソッドはLargesの最小値をSmallsに移動させ、ToLargesメソッドはSmallsの最大値をLargesに移動させます。

上位グループと下位グループ内の更新

配列の要素が更新されたときは対応する要素が Larges と Smalls のどちらにあるのかを調べて見つかった要素を無効にします。そして新しい値がセットされた要素を追加しなおします。

そのあとLargesの最小値と Smallsの最大値を比較します。最大値と最小値はPriorityQueue.Peek()を実行すれば取得できるのですが、取得されたものが無効なものであれば有効なものが見つかるまで取り除きます。比較対象が見つかったら比較して必要であれば入れ替えの処理をおこないます。

処理をしたあと上位 K 番目までの総和を取得します。

仕上げ

あとは上記で定義したSolverクラスで解を求めるだけです。