LoginSignup
1
0

std::setが欲しくなる問題をPythonで解く

Last updated at Posted at 2021-10-09

追記(2024/03/21)

AtCoderでもstd::setライクなライブラリが使えるらしいです。手前味噌ですが、それについてのポエムを書いたので、よかったら見てね。

はじめに

この記事は投稿者のメモ代わりに書かれています。不具合があったらごめんね。
ACコードもあまりリーダブルじゃないので参考にしないように。

この記事は何?

競プロでは、配列の最大/最小値を求めたり、配列上で二分探索をしたりすることがよくあります。配列の要素が変わらなければ簡単ですが、要素が挿入されたり削除されたりすることもあります。 頻繁ではありませんが。 こんなとき、いちいちソートしていては間に合いません。C++ならstd::setを使えますがPythonにはありません。
以下ではstd::setの代わりに使える かもしれない 方法を書きます。

heapqを知っていますか?

ABC137D - Summer Vacation
いきなり脱線しますが優先度付きキューを紹介します。優先度付きキューとは、appendが$O(\log N)$、popが$O(\log N)$、最小値の参照が$O(1)$で済むデータ構造です。
Pythonでは標準ライブラリのheapqが使えます。Aをheapifyしたとき、$A[0]$がAの最小値です。最大値を求めたい場合はappendする値とpopされた値に$-1$を掛けます。
公式解説が一番わかりやすいので読んでください。
ACコードの例

類題

heapqから削除したつもりリストを作って管理する

ABC170E - Smart Infants
こちらのブログの解説が非常にわかりやすいです。
幼児$i$が幼稚園Aの最強園児である時、$i$がAからBへ転園すると、Aの最強園児が入れ替わります。つまりAから$i$を削除したあとの最強園児を求めなくてはなりません。
これを解決するのが、削除したつもりの園児のリストを作成する方法です。
つまり、最強園児を求める際に、heapqの先頭に来た園児に対して削除したつもりかチェックし、そうであるならば本当に削除します。そうでないならば、その園児が最強です。最強園児が決まるか、園児が0人になるまで繰り返します。listでは本当に削除する時の処理が遅いため、dictで要素ごとに個数を管理します。
クエリを処理するごとに平等さを求める必要がありますが、同様にheapqと「削除したつもりリスト」で管理できます。
ACコードの例

配列を木構造に落として総和をメモする

ARC033C - データ構造
BIT(Fenwick Treeとも)というデータ構造を使えば、区間和を$O(logN)$で求めることができます。また、平方分割を使えば、区間和を$O(\sqrt{N})$で求めることができます。
$A_{X_i} = (S \in X_i) \ ?\ 1 :0$として、右端のindexが0で総和がxとなる最小の区間を求めれば解けます。
$1\le X_i \le 2 \times 10^5$なので、$X_i-1$をそのままindexとして配列に格納しても間に合います。
二分探索で求めれば間に合うそうですが、私のコーディングスキルでは残念ながらTLEになってしまいました。平方分割で線形探索を行うと通りました。
ACコードの例

類題

座標圧縮+木

ABC217D - Cutting Woods
簡単のために$x=0,L$にも切れ目があることにします。
$1\le L \le10^9$なので、$L$をそのままindexとして配列に格納すると間に合いません。
そこで、考えるべき線の数が高々$Q+2$本であること、線の左右関係は問題を通じて変わらないことを利用して、座標圧縮を行います。座標圧縮とは、値の上下関係を維持したまま圧縮し、圧縮後の値について操作を行うテクニックです。ただし、あらかじめ全ての値を知っている必要があるため、クエリ先読みを行います。
抽象的な書き方をすると$f(X)=[0,\#X)\cap\mathbb{Q}$かつ$a_i<a_j\Leftrightarrow f(a_i)<f(a_j)$
を満たす$f$と$f^{-1}$を考えるのですが、具体的には重複を取り除いた後にソートしてindexとvalueを入れ替えてdictに入れればよいです。提出したコードでは難解な書き方になっていますが、簡単に書くとこうなります。

X = set()
for c,x in Q:
	X.add(x)
finv = list(sorted(X))
f = dict()
for k,v in enumerate(X):
	f[v] = k

あとはBITなり平方分割なりに乗せてください。BITも二分探索もしんどいので私は平方分割と線形探索を行いました。だいぶループ回してるはずなのに通るとは思わなかった。
ACコードの例

巨人の肩に乗る

likein12さんがPython向けのordered setとmultisetのwrapperを公開しています。
バイナリを埋め込む手法が使われているため、コンテストのルールをよく確認して使用してください。

自力で実装する

頑張って実装しても遅いことが稀によくあります。

いかがでしたか

C++書けるならそっちのほうがいいよ。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0