Processing math: 100%

マスログ

競技プログラミングのススメ

公開日

2022年6月9日

更新日

2022年6月9日


こんにちは。和から数学講師の栗原です。

最近競技プログラミングにハマっており、AtCoderというサイトで毎週のようにコンテストに参加しています。競技プログラミングでは数学色の強い問題が出題されることも多く、身につけた知識を実践形式でアウトプットできるとても良い場だと思います。

今回はそんな競技プログラミングの魅力の一端をお伝えすべく、数学色の強い問題を 1 問取り上げ、その解説をしてみたいと思います。ではさっそく問題を見ていきましょう。

1.問題

出典:AtCoder Regular Contest 113 B – A^B^C

以下が問題文です(少し表現を変えています。)

正の整数A,B,Cが与えられます。A^{B^C}10進法での1の位を求めてください。
ただし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からは410回引くことができ最後に3余ります。したがって、{23}^{43}1の位は{23}^31の位と同じ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からは231回引くことができ1余ります。これに倣ってCから遡って考えると
{43}^{63},{43}^{61},{43}^{59},{43}^{57},\ldots,{43}^1
4で割った余りは全て同じです。したがって、{43}^{63}4で割った余りは{43}^14で割った余りと同じ3であることがわかりました。

{43}^{63}4で割った余りが3なので、2節と同様に考えることで、{23}^{{43}^{63}}1の位は{23}^31の位と同じであることがわかります。2 節で計算したようにこれは7でしたから、求める答えが7だとわかりました。

4.一般のA,B,Cの場合

ではいよいよ一般のA,B,Cについて考えていきましょう。

3節での観察より、Aの累乗の1の位(10で割った余り)と、Bの累乗を4で割った余りに注目すれば良いはずです。

まずはAの累乗について一括で確認してみましょう。縦方向にA 、横方向に何乗したか( 011 )を並べます。

  
 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、横方向に何乗したか( 011 )を並べます。

  
 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で割った余りは異なる可能性があるので注意) よって CR_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の位は異なる可能性があるので注意) よってXR_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.まとめ

いかがでしたでしょうか。

具体例を作成、観察し、そこから規則性を見抜き、その理由を考える。それが一般化できるなら解法に繋げ、それを実現するプログラムを作る。
なんだかちょっとした研究のようですね。

競技プログラミングに興味をお持ちになった方は是非とも挑戦してみてください。ここまで読んでいただいてありがとうございました。

<文/栗原>

新着記事

同じカテゴリーの新着記事

同じカテゴリーの人気記事

CONTACTお問い合わせ

個別講義や集団講義、また法人・団体向けの研修を行うスペース紹介です。遠人に在住の方や自宅で講義を受けたい方はオンライン講座をご用意しております。よくある質問はこちら