はじめに
以前の記事で、python
においてelement in list
という書き方は非常に遅いので気を付けましょうと書きました。これについて、実例とともに見ていきたいと思います。
element in list
について
python
では、リストにある要素があるかどうかを次のように判定することが可能です。
>>> 1 in [1, 2, 3] # True >>> 4 in [1, 2, 3] # False
ただし、この処理は非常に遅いことが知られています(おわりに参照)。
例題
ABC129-C を扱います。まだ問題を解いたことがない方はネタバレ注意です。
問題概要
N
段の階段があり、現在、0段目にいます。一度に移動できるのは、一段もしくは二段です。いま、a1, a2, ..., aM
段目の床は壊れていて足を踏み入れることができません。壊れている床を踏まずにN
段目までたどり着く方法は全部で何通りあるか求めてください。ただし、答えは10^9 + 7
で割った余りを提出してください。問題へのURL
制約
1 <= N <= 10^5
0 <= M <= N - 1
1 <= a1 < a2 < ... < aM <= N - 1
解答
基本的な方針としては、dp
テーブルを用意してあげて、dp[i]
にi
段目の階段にたどり着くための移動方法(を10^9 + 7
で割った値)となるように構成してあげればよいと思います。このとき、床が壊れているような階段k
に対して、dp[k] = 0
となることに注意をしながら、dp
テーブルを埋めていってあげます。
はじめに、TLE
になってしまう書き方を提示します。
n, m = map(int, input().split())
a = [0] + [int(input()) for _ in range(m)]
MOD = 10 ** 9 + 7
dp = [0] * (n + 1)
dp[0] = 1
# 配列 a に 1 が含まれているかどうか
if 1 in a:
dp[1] = 0
else:
dp[1] = 1
# dp デーブル埋め
for i in range(2, n + 1):
if i in a:
continue
dp[i] = dp[i - 1] + dp[i - 2]
dp[i] %= MOD
print(dp[-1]) # dp[n] と同じ
このコードをPython3
で提出するとTLE
となってしまいます。これは、dp
テーブルを埋める際に、毎回の反復でi
が配列a
に含まれているかどうかの判定を行っていることが原因です。
そこで、次のように書き方を変更してあげましょう。すなわち、毎回の反復でi
が配列a
に含まれているかどうか判定するのではなく、事前に床i
が壊れているかどうかのboolean
配列を用意してあげて、それを利用しましょう。
n, m = map(int, input().split())
a = [0] + [int(input()) for _ in range(m)]
MOD = 10 ** 9 + 7
ok = [True] * (n + 1) # 床 i が壊れている ⇒ False
for v in a: # もちろん入力の段階で処理をしてもよい
ok[v] = False
dp = [0] * (n + 1)
dp[0] = 1
# 配列 a に 1 が含まれているかどうか(ここで一度 in を使うくらいはいいでしょう)
if 1 in a:
dp[1] = 0
else:
dp[1] = 1
# dp テーブル埋め
for i in range(2, n + 1):
if ok[i] == True: # 変更箇所
dp[i] = dp[i - 1] + dp[i - 2]
dp[i] %= MOD
print(dp[-1]) # dp[n] と同じ
これをPython3
で提出してあげると、200ms
くらいでAC
となります。
おわりに
おわりに、element in list
という書き方がどれくらい遅いのか、jupyter notebook
で確認しておきましょう(思い付き)。
次のようなコードで実験してみましょう。lst
という配列の要素に-1
があるかどうかを判定する操作を1000
回行うコードです。
なお、lst
は事前に
lst = list(range(10 ** 5))
のように定義しておきます。
%%timeit
for i in range(1000):
if -1 in lst:
print('')
結果は次のようになりました。
1.67 s ± 211 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
平均で1.67 s
かかっています。今回は反復回数を1000
としましたが、上で扱った問題では最高で10 ** 5
回程度の反復を要します(N
の制約条件より)。TLE
になるのも納得できます。