AtCoder Beginner Contest 164 D を Python で
AtCoder Beginner Contest 164 D を Python で解説していきます。
この問題はコンテスト中かなり頭を悩ませたのですが,どうやら ABC 158 E 問題にとても似ている問題だったようですね。とても数学的要素が強い問題かと思いますが,数学が得意でない人や灰コーダー向けに重要な点を抜き出して解説していくので,頑張って理解していきましょう!
それでは,まずは問題文からいきます。
実行時間制限: 2 sec / メモリ制限: 1024 MB
配点 : 400 点
問題文
1
から 9
までの数字のみからなる文字列 S が与えられます。
次のような条件を満たす整数の組 (i,j) (1≤i≤j≤|S|) の総数を求めてください。
条件: S の i 文字目から j 文字目までを 10 進法の整数としてみると、この整数は 2019 の倍数である。
制約
- 1≤|S|≤200000
- S は 1 から 9 までの数字のみからなる文字列
入力
入力は以下の形式で標準入力から与えられる。
S
出力
条件を満たす整数の組 (i,j) (1≤i≤j≤|S|)の総数を出力せよ。
入力例 1
1817181712114
出力例 1
3
条件を満たすのは (1,5),(5,9),(9,13) の 3 個です。
入力例 2
14282668646
出力例 2
2
入力例 3
2119
出力例 3
0
条件を満たす整数の組は存在しません。
解説
もちろん,以下のように問題文に沿ってそのまま愚直な二重ループで書くやり方は,O(N^2)で,
(オーダーの感覚は,大体10^8以 下となることを目安にプログラムすれば間に合います。)
また,詳しく言うと,S は最大 200000 桁ですが,コンピュータ内では64 bit の整数ですら 18 桁くらいしか表現できないので(2^(63-1)という数字になります)普通の整数型では表せず,以下のような単純な実装ではかなり重い計算になってしまうようです。(Python では内部処理で無理やり数を出すこと自体はできます)もはやオーダーがどうこうという話ではなくなります。😱
s = input()
n = len(s)
count = 0
for i in range(n-3):
for j in range(i+3, n):
number = int(s[i:j+1])
if number % 2019 == 0:
count += 1
print(count)
ということで,何らかの工夫が必要になるわけです。
まず,このような区間の数え上げは,片方を固定して,もう片方を高速で列挙するのが定石です。
具体的に見ていきましょう。入力例1を短くして 181718171 を用いて扱います。このとき,
(
で得ることができます。
これはつまり、(a_4-a_7)/10^(9-6)を表していることになりますよね。つまり,
さて,ここからが本題です。先ほどの式
これはつまり,
以上より,1~N までの全ての
そのために,これまで𝑖 は 1から順に N までという順番で考えていましたが,逆に N から 1 までの順番で考えてあげます。この順に,
では,
として,
を足すことで得ることができます。このように考えると,ループの中で d を毎回 10 倍するような値に設定して,毎回対応する S[-i]×d をその前の値に足していくと,必要な
ここまでは以下のようなコードで表せます。
# a_iはこのnumber*dで表していく
number = 0
d = 1
for i in s:
# a_iを表現
number += int(i)*d
# numberで次の桁にしたいから,dを10倍
d *= 10
# numberが大きすぎない値になるようにd自体を先にmod2019の値でとる
d %= 2019
次に,mod(2019) の値の数え方についてです。これは まず0 が 2019 個入ったリスト(名前をcountとする) を用意します。そして各
また,最初の段階で,便宜上何もとらないという意味として
ここまでを書くと,以下のようになります。
s = input()
# sを逆にする
s = s[::-1]
n = len(s)
# 各インデックス番号にmod2019の値が格納されるリストを設定
count = [0]*2019
# a_0の分を入れておく
count[0] = 1
# a_iはこのnumber*dで表していく
number = 0
d = 1
for i in s:
# a_iを表現
number += int(i)*d
# a_iのmod2019の値をcountに格納
count[number%2019] += 1
# numberで次の桁にしたいから,dを10倍
d *= 10
# numberが大きすぎない値になるようにd自体を先にmod2019の値でとる
d %= 2019
後はこの count の中に格納された値について,取りうる場合の数の和を出します。これは,count のそれぞれのインデックス番号に対する値について,異なる2つをとる場合の数なので,
以上,詳しく説明しすぎて逆によく分かりにくくなってしまった感がありますが,いかがだったでしょうか。😅
それでは,解答例に移ります。
解答例
s = input()
s = s[::-1]
n = len(s)
count = [0]*2019
count[0] = 1
number = 0
d = 1
for i in s:
number += int(i)*d
count[number%2019] += 1
d *= 10
d %= 2019
ans = 0
for i in count:
ans += i*(i-1)//2
print(ans)
個人的に,今までで一番説明が難しく,冗長になってしまったような気がしますが,どうしょうか。ABC 158 E とほぼ同じだったようで,自分もこの回のコンテストには参加していたので,解けなかったのが悔しかったです。もし分からないところや,間違っているところがあれば,twitter やここのコメントでお伝えください。
それでは!