競技プログラミングのススメ
公開日
2022年6月9日
更新日
2022年6月9日

こんにちは。和から数学講師の栗原です。
最近競技プログラミングにハマっており、AtCoderというサイトで毎週のようにコンテストに参加しています。競技プログラミングでは数学色の強い問題が出題されることも多く、身につけた知識を実践形式でアウトプットできるとても良い場だと思います。
今回はそんな競技プログラミングの魅力の一端をお伝えすべく、数学色の強い問題を 1 問取り上げ、その解説をしてみたいと思います。ではさっそく問題を見ていきましょう。
この記事の主な内容
1.問題
出典:AtCoder Regular Contest 113 B – A^B^C
以下が問題文です(少し表現を変えています。)
ただしA,B,Cは制約
・1\leq A,B,C \leq {10}^9
を充たすものとし、実行時間制限は 2 秒とします。
この問題を解くプログラムを書くことが目標となります。
A^{B^C}はとんでもなく大きな数になり得るので、いくらコンピュータとはいえ「単に計算するだけ」とはいきません。どのように工夫すれば 2 秒以内に答えを求めることができるでしょうか。
それでは解説に移ります。なお、解説中に記載してあるPtyhonのコードは Google Colaboratory や Jupyter Notebook上で実行してみることができます。
2.A=23, B=43, C=1のとき
いきなりA^{B^C}を考えるのは難しいので、まずはC=1のケースを考えてみましょう。さらに考えやすくするため、 A=23,B=43の場合を例にして考えていきます。
a = 23
result = []
for b in range(12):
result += [a**b]
result
---出力----
[1,
23,
529,
12167,
279841,
6436343,
148035889,
3404825447,
78310985281,
1801152661463,
41426511213649,
952809757913927]
こういう観察が手軽にできるのが Python の良いところですね。計算結果の1の位の数は1, 3, 9, 7, 1, 3, 9, 7, 1, 3, 9, 7, \ldots となっています。よく見ると4つずつの繰り返しになっていることに気づきます。
このことを納得するには、筆算で計算するときの手順をイメージしてみると良いでしょう。例えば529の次が12167なのは529\times 23=12167だからですが、これを筆算で計算するとき、9×3=27から計算し始めるはずです。その後がどうであれ、計算結果の1の位が7になることはこの時点で確定しています。
結局1の位だけを計算していくと、
- ・1×3=3
- ・3×3=9
- ・9×3=(2)7
- ・7×3=(2)1
となり、4 回で元の1に戻ってきます。以降は同じことの繰り返しになります。
さて、4つずつの繰り返しになるとわかったので、Bから遡って考えると、
{23}^{43},{23}^{39},{23}^{35},{23}^{31},\ldots
の1の位は全て同じだということになります。
43\div 4=10 \mbox{あまり} 3
なので、43からは4を10回引くことができ最後に3余ります。したがって、{23}^{43}の1の位は{23}^3の1の位と同じ7であることがわかりました。
このくらいなら単純計算もできるため、念のため確認しておきましょう。
23**43
---出力----
35834136918934220777541995677272642015423987712183913488967
確かに1の位は7で合っていますね。
3.A=23, B=43, C=63のとき
次はC=63にしてみましょう。
もはや{23}^{{43}^{63}}はコンピュータの力を持ってしても簡単に計算できる大きさではありませんが、2節での観察で見た通り、{43}^{63}を4で割った余りさえわかれば答えは求まります。
ところで2節で求めた{23}^{43}の1の位は、{23}^{43}を10で割った余りとも言い換えられます。こう言い換えてみると、{43}^{63}を4で割った余りも先と同じ方法で求められそうな気がします。
{43}^0から{43}^{11}までを4で割った余りを観察してみます。
b = 43
result = []
for c in range(12):
result += [(b**c) % 4]
result
---出力----
[1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3]
期待通り、4で割った余りは1, 3, 1, 3, \ldots と2つずつの繰り返しになっていますね。
63\div 2=31\mbox{あまり}1
ですから、63からは2を31回引くことができ1余ります。これに倣ってCから遡って考えると
{43}^{63},{43}^{61},{43}^{59},{43}^{57},\ldots,{43}^1
を4で割った余りは全て同じです。したがって、{43}^{63}を4で割った余りは{43}^1を4で割った余りと同じ3であることがわかりました。
{43}^{63}を4で割った余りが3なので、2節と同様に考えることで、{23}^{{43}^{63}}の1の位は{23}^3の1の位と同じであることがわかります。2 節で計算したようにこれは7でしたから、求める答えが7だとわかりました。
4.一般のA,B,Cの場合
ではいよいよ一般のA,B,Cについて考えていきましょう。
3節での観察より、Aの累乗の1の位(10で割った余り)と、Bの累乗を4で割った余りに注目すれば良いはずです。
まずはAの累乗について一括で確認してみましょう。縦方向にA 、横方向に何乗したか( 0~11 )を並べます。
result = []
for a in range(10):
result += [[]]
for b in range(12):
result[-1] += [(a**b) % 10]
result
---出力----
[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 2, 4, 8, 6, 2, 4, 8, 6, 2, 4, 8],
[1, 3, 9, 7, 1, 3, 9, 7, 1, 3, 9, 7],
[1, 4, 6, 4, 6, 4, 6, 4, 6, 4, 6, 4],
[1, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
[1, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
[1, 7, 9, 3, 1, 7, 9, 3, 1, 7, 9, 3],
[1, 8, 4, 2, 6, 8, 4, 2, 6, 8, 4, 2],
[1, 9, 1, 9, 1, 9, 1, 9, 1, 9, 1, 9]]
どの行を見ても、2列目以降は1つずつ、2つずつ、または4つずつの繰り返しになっています。面倒な場合分けを防ぐため、いずれも4つずつの繰り返しになっているとみなすことにしましょう。
次はBの累乗について一括で確認してみましょう。縦方向にB、横方向に何乗したか( 0~11 )を並べます。
result = []
for b in range(4):
result += [[]]
for c in range(12):
result[-1] += [(b**c) % 4]
result
---出力----
[[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
[1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3]]
どの行を見ても、3列目以降は1つずつまたは2つずつの繰り返しになっています。こちらも面倒な場合分けを防ぐため、いずれも2つずつの繰り返しになっているとみなすことにしましょう。
後は 3節とほとんど同じですので、以下に解法としてまとめましょう。
5.解法
解法をまとめます。
C\geq 2のとき、
C\div 2=Q_C\mbox{あまり}R_C(0\leq R_C <2)
だとすると、
B^C,B^{C−2},B^{C−4},\ldots,B^{R_C+2}
を4で割った余りは同じです( B^{R_C}を4で割った余りは異なる可能性があるので注意)
よって CをR_C+2に置き換えてしまうことにします。これによって問題の答えが変わってしまう心配はありません。
X=B^Cとおきます。 X\geq 4 のとき、
X\div 4=Q_X\mbox{あまり}R_X(0\leq R_X <4)
だとすると、
A^X,A^{X−4},A^{X−8},…,A^{R_X+4}
の1の位は同じです。( A^{R_X}の1の位は異なる可能性があるので注意)
よってXをR_X+4に置き換えてしまうことにします。これによって問題の答えが変わってしまう心配はありません。
以上の置き換えを行った上でA^Xを計算すれば、その1の位が答えとなります。
6.実装例
以下 Python3での実装例です。
a, b, c = map(int, input().split())
if c >= 2:
c = c % 2 + 2
x = b ** c
if x >= 4:
x = x % 4 + 4
print((a ** x) % 10)
7.まとめ
いかがでしたでしょうか。
具体例を作成、観察し、そこから規則性を見抜き、その理由を考える。それが一般化できるなら解法に繋げ、それを実現するプログラムを作る。
なんだかちょっとした研究のようですね。
競技プログラミングに興味をお持ちになった方は是非とも挑戦してみてください。ここまで読んでいただいてありがとうございました。
<文/栗原>