出典
誰でも参加できる2週間に渡るTwitter難問チャレンジ
— 数学夏祭り@絶賛開催中🎇 (@mathmatsuri) 2020年8月31日
数学夏祭り 記念すべき第一問!
「解答する、拡散する、解説する」
それぞれにキャンペーンプライズを進呈!
みんなで祭りを盛り上げよう!#数学夏祭り#数学夏祭り問1
参加方法は↓リプに続きます。 pic.twitter.com/CD1JT7Txax
攻略法
結論から言うと、この問題の攻略法は、「コンピュータプログラムを組む」が適切であると考える。
ただし、ただ闇雲に組むだけでは駄目である
- なぜコンピュータプログラムを組むことが適切なのか
- どのようなプログラムを組めば良いのか
なぜコンピュータプログラムを組むことが適切なのか
それは普通に問題を解いてみればわかる
通分すると
$$pqr=79(p+q)$$
両辺にを掛けて整理すると
$$(pr-79)(q r-79)=79^2$$
ここで、であるため、となる。つまり以下の可能性だけが残る。(79が素数であることを利用)
$$(pr-79,q r-79)=(1,79^2),(79,79),(-79,-79),(-79^2,-1)$$
(追記:両方とも負の場合を列挙するのを忘れてました。しかしその場合はとなるため不適となります。以降の議論に影響は出ません。)
後半2つは不適であるため、以降は前半2つだけを考える。
のときは、より、である。
(注意:であるため、の可能性は消える)
のときは、よりとなる。
は80の約数を全列挙してみればよい。よってとなりそうだが、であるため、を除外しなくてはいけない。ここを見落としてミスした人も少なくないと考える。よっては80の正の約数のうち、1を除いたもの全てを見ればよい。そうすると、
$$\frac{1}{p}+\frac{1}{q}=\frac{80}{79p}=\frac{r}{79}$$
より、が「80の約数のうち、1以外のもの」ならば、解の十分性も満たしている。
つまり、手計算で求めようとすると、を除き忘れるというミスが起こりやすくなるのである。
プログラムを組めばいいという理由はもう一つある。このようにして解を全列挙した時、について並べ替えなければいけない。すると
$$p=2,4,5,8,10,16,20,40,79,80,158$$
となり、がであることから生成できた解とであることから生成できた解が混じっている。これらの解をミスなく並べ替えるためにはコンピュータプログラムに頼るのが適切である。
ちなみに答えは25280である。ここで、
「前から3番目の」は16であり、()
「後ろから5番目の」は1580となっている。()
結局の所、このような先着5名が表彰されるようなタイムアタック形式で、このようなケアレスミスを誘発しやすい問題が出題された場合、コンピュータプログラムを組んで突破するのが適切である。電卓の使用はルールの範囲内であるため堂々と使って大丈夫である。
どのようなプログラムを組めば良いのか
コンピュータの使用がルール上問題ないと言っても、正しく使いこなせるようにならなくてはいけない。
解を探す上で、ぱっと思いつく方法は全探索である。しかし、ただ闇雲に全探索してはいけない。
まず、解かどうかを判定するプログラムに注意が必要である。
#ダメな方法 def isanswer(p,q,r): if 1/p+1/q==r/79 and p<=q and r<79: return True else: return False #よい方法 def isanswer(p,q,r): if (p*q*r)==79*(p+q) and p<=q and r<79: return True else: return False
なぜ前者がダメで後者がOKなのかというと、浮動小数点演算の誤差を考慮してないからである。ここでは詳しい説明は省くが、要は「プログラミングで小数を扱う場合は誤差が生まれる」のである。そして==という演算子で判定するため、誤差が生まれると正しく判定できなくなる。
これを避けるためには、整数での演算に帰着する必要がある。(ここで正しく式変形できるかが重要になる)
次に全探索する範囲について考える必要がある。
前節での式変形を流用すると
$$(pr-79)(q r-79)=79^2$$
となる。ここで、は整数であるため、以下の不等式が成り立つ。
$$(pr-79)^2\leq (pr-79)(q r-79)=79^2$$
$$ p-79\leq (pr-79)\leq 79$$
よってで探索すればよいことになる。
についてはとは別の方法を使う。
$$ q-79\leq (q r-79)\leq 79^2$$
より、
$$ q\leq 79^2+79$$
となる。よってで探索すればよい。
については、で探索すればよい…というわけではない。
は等式として候補が決まっているため、十分性を満たしているかの判定だけでよい。
具体的にはこうなる。
def isanswer(p,q): if (79*(p+q))%(p*q)==0: r=(79*(p+q))//(p*q) if p<=q and r<79: return True return False
つまり、の2変数だけ決まればの値は自動的に決まるため、について78個の自然数を探索する必要はない。
結局の所以下のようなプログラムを組めば答えを出力してくれる。
for p in range(1,159): for q in range(1,79*80+1): if (79*(p+q))%(p*q)==0: r=(79*(p+q))//(p*q) if p<=q and r<79: print(p,q,r)
出力結果は以下のようになる。
2 158 40 4 316 20 5 395 16 8 632 10 10 790 8 16 1264 5 20 1580 4 40 3160 2 79 79 2 80 6320 1 158 158 1
これでめでたくすべての解を出力してくれる。
探索範囲はである。この数字がよりも小さいオーダーであるため、プログラムの実行時間は現実的な時間となる。これをについても全探索するとくらいの計算量になるため、プログラムの実行に少し時間がかかってしまう。
効率的なアルゴリズム
一般化してみる。
を奇素数として、
$$\frac{1}{p}+\frac{1}{q}=\frac{r}{N}$$
を満たす自然数を全列挙せよ
という問題について考える。前述のアルゴリズムだと、計算量はとなっている。今回出題された問題ではなので現実的な時間で終わったが、が大きくなっても解けるようなアルゴリズムを考えてみる。
ここで、同様の手法によって
$$(pr,q r)=(N+1,N^2+N),(2N,2N)$$
となっている。の場合には、のみであるため、
となる。(の約数がの4つのみであることから従う。)
については約数全列挙を使えばよい。
ここで、の約数の全列挙はで行うことができる。
これは、という正の約数はという約数とセットになっていて、
とのどちらかはより小さいため、だけチェックすればOKだからである。すると以下のようになる。
N=int(input()) i=1 while(i*i<=N): if N%i==0: print(i) if i*i!=N: print(N//i) i+=1
これでの正の約数すべてを出力してくれる。
つまり、この問題を解くためには
①の約数を全列挙()
②の約数についてループを走らせる
③ について、が解の候補である。
④ かどうかを確認する
⑤生成した解全てについてでソートする
計算量はであるため、くらいなら現実的な時間で解いてくれる。*1
結局プログラムを書くと以下のようになる。
N=int(input()) D=[] i=1 while(i*i<=(N+1)): if (N+1)%i==0: D.append(i) if i*i!=(N+1): D.append((N+1)//i) i+=1 ans=[(2*N,2*N,1),(N,N,2)] for d in D: p,q,r=d,N*d,(N+1)//d if r<N: ans.append((p,q,r)) ans.sort() for p,q,r in ans: print(p,q,r)
このプログラムは、例えばを入力に入れても、プログラムの実行は現実的な時間で完了する。
一方、の方のプログラムにを突っ込んだ場合、恐らく宇宙の年齢に匹敵する実行時間がかかるだろう。