AtCoder Beginner Contest 217をPythonで解く

投稿日: 

Atcoder Python

B!
Daniel AgreloによるPixabay(https://pixabay.com/)からの画像

こんにちは, Shinoryoです.

今回はAtCoder Beginner Contest 217を, Pythonで解いてみた結果を書き連ねていこうと思います.

A - Lexicographic Order

Pythonにおいて辞書順は, 通常の大小関係<>で表されます.

B - AtCoder Quiz

方法はさまざまあるかと思いますが, 以下では「コンテスト一覧となる集合から差し引いた残りを出力する」という方法で記述しています.

C - Inverse of Permutation

例えば, $[p[i], i]$を$i = 1, 2, \ldots, N$まで並べたリストを用意して昇順にソートすると, 各要素の2番目の要素を取り出したものは順列$Q$に相当します(図1参照).

図1 $[p[i], i]$を並べたリストをソートした概念図.

時間計算量は$O(N \log N)$に(たぶん)なりますが, これでもACすることができます.

あるいは, $q[p[i]] = i$であることに気づいてしまえば, より高速に処理することが可能です(「$Q$の$p[i]$番目の要素が$i$である」そのままです). 時間計算量は$O(N)$に(たぶん)なります.

D - Cutting Woods

UnionFindを利用した解法は, おそらく以下のようになるかと思います. UnionFindは, 「AtCoder Beginner Contest 214をPythonで解く」でも取り扱っています.

  1. クエリが全部なされた後でも切断されていない部分を切断するような, 新たなクエリを加える. これにより, 全てのクエリが実行された後には全ての部分が切断されている状態になる.
  2. $L$で初期化されたUnionFindを用意する.
  3. クエリを逆に実行していく. $c = 1$なら切断の逆の結合を行い, $c = 2$なら出力するべき値を何らかのリスト等に格納しておく.
  4. 出力するべき値が格納されたリストから, 定められた順番で出力を行う.

Pythonでは, これだとTLEになってしまいます.

そこで, 発想を変えてみます. 「切断位置リスト」として, 今までに切断された位置が格納されたリストを用意します. 便宜上, $0$と$L$も初期値として格納しておきます. あるクエリ$c = 2, x = x_1$が与えられたときに,

  • 「切断位置リスト」に含まれる要素のうち$x_1$より大きい要素のうち最小の要素($= x_2$)
  • 「切断位置リスト」に含まれる要素のうち$x_1$より小さい要素のうち最大の要素($= x_3$)

を高速に取得できれば, 出力すべき値は$x_2 - x_3$となります.

そのような$x_2$, $x_3$を高速に取得できる方法の1つに, bisectモジュールがあります. リストの順序関係(基本的には, 数値の大小関係)を保ったまま, リストへの挿入を行うことができます. 挿入だけでなく, 「挿入可能な位置」を調査することができ, これによって$c = 2$のクエリに関する操作を行うことができます.

これだけでも残念ながらPythonだとTLEになってしまうのですが, arrayモジュールを利用することによって, 挿入をやや高速に行うことができて*1 ACすることができます.

E以降

E以降は私の能力不足故に省略いたします.

参考にしたサイト等

脚注

*1 : 気になったので, リストに対してinsertをしたときの処理時間を計測してみました.

通常のリストの場合:3.3825604915618896秒
arrayモジュールの場合:0.6667544841766357秒

こうしてみると, (少なくとも$N$が十分に大きい場合には)確かにarrayモジュールの方がinsertに関しては高速に処理が出来そうですね.