午後のひとときに、数学の問題をプログラミングで解いてみよう。
問題
すべての角が等しいn角形において、n個の辺の長さが1からnまでのすべての整数となるものは存在するのか?
存在するならば列挙し、個数を求めよ。
三角形では、題意の等角より内角は180˚-(360˚/3)=60˚となり、つまりは正三角形であるから、辺の長さが等しくなってしまうので、不適。
同様に四角形では、題意の等角より内角は180˚-(360˚/4)=90˚となり、つまりは長方形であるから、対辺の長さが等しくなってしまうので、不適。
ここまでは、容易に存在しないことが証明出来る。
五角形は後回しで、
六角形は考えやすいと思う。
1辺が1の正三角形で敷き詰められている方眼紙のようなものを考える。
最初に辺の長さ1を描き、時計回り/反時計回りどちらでもよいが、
辺の長さを変化させて、方眼に沿ってつなげていく。
※初期値は必ず1である必要はない。
最初の辺を1と固定したことで、回転解は省かれる。
あと省きたいのは鏡像解である。
1に隣り合う辺の長さで、時計回り側が半時計回り側よりも小さいといった条件をつければよい。
六角形の解は、
1 4 5 2 3 6
1 5 3 4 2 6
の2つが見つかる。
机上や頭の中ではこれくらいが限界であろう。
さて、プログラミングでやるならば、どうしましょうか。
辺の長さの配列が必要ですね。
後は三角関数を扱うので誤差を考える必要がありますね。
汎用性を持たせるために、多重ループではなく、再帰処理にしましょうか。
原点(0,0)からスタートし、辺の長さ1を(1,0)として、時計回りに辺を構築していくとして、
m角形だとして、辺の長さを格納している配列をl[]とすると、
x = 0.0;
y = 0.0;
a = 360.0/m;
for (i=0; i<m; i++) {
x += l[i]*cos(a*i*M_PI/180.0);
y -= l[i]*sin(a*i*M_PI/180.0);
}
こんな感じでループさせて、(x,y)と原点との距離でレンジ内に収まっているかどうかで判断しましょうか。
レンジをr、レンジ内に収まった解の個数をcとすると、
z = sqrt(x*x+y*y);
if ( z < r ) {
c++;
for (i=0; i<m; i++) printf("%2d ", l[i]);
printf("%23.20lf %23.20lf %23.20lf\n", x, y, z);
}
の様に、辺の長さの2桁で揃えて列挙、
終点のx, yの値、始点と終点との距離zを小数点以下20桁まで、
レンジは最大でも1未満くらいにして、整数部は0で、-符号がつくことも想定させ、
符号、小数点を含めて+3桁して、23桁で表示させることにする。
これでスペースで揃えられたデータが列挙されるので、エクセルに取り込みやすいだろう。
プログラム名はpolygon
第1パラメータに角数
第2パラメータにレンジ
とすれば汎用的に使えるでしょうね。
後は、速度的な問題として、角数mが大きくなったら、それだけ深くなるので、指数的に遅くなるんだろう。
多少は速度アップを考えて、
再帰処理の関数を
next_side(int n)
として、
l[0] = 1;
next_side(1);
と呼び出して、
再帰処理内では、
if ( n == 1 ) {
for (i=2; i<m; i++) {
l[1] = i;
next_side(2);
}
} else if ( n == 2 ) {
for (i=l[1]+1; i<=m; i++) {
l[m-1] = i;
next_side(3);
}
} else if ( n < m-1 ) {
for (i=2; i<=m; i++) {
for (j=1; j<n-1; j++) {
if ( l[j] == i ) break;
}
if ( j == n-1 && l[m-1] != i ) {
l[n-1] = i;
next_side(n+1);
}
}
} else {
// レンジ内判定
}
こんな感じでしょうか。
プログラムのたたき台が出来たので、六角形を検索させてみた。
1 4 5 2 3 6 0.00000000000000066613 -0.00000000000000133227 0.00000000000000148952
1 5 3 4 2 6 0.00000000000000066613 -0.00000000000000133227 0.00000000000000148952
6-poygon 2
六角形でこれだけの誤差が出てしまっている。
この2解以外は0.1から外れているということですね。
さて、プログラム内部ではどれくらいの試行をするかというのを考えてみる。
1から6までなので 6!=720、
回転解を除くと (6-1)!=5!=120
鏡像解を除くと (6-1)!/2=5!/2=60
となる。
つまり、60パターンの中から2パターンが抽出されたということですね。
m角形では、(m-1)!/2パターンの試行ということです。
m=3 p=1
m=4 p=3
m=5 p=12
m=6 p=60
m=7 p=360
m=8 p=2520
m=9 p=20160
m=10 p=181440
m=11 p=907200
m=12 p=19958400
m=13 p=239500800
m=14 p=3113510400
m=15 p=43589145600
m=16 p=653837184000
m=17 p=10461394944000
m=18 p=177843714048000
…
これは、階乗的に増大していくので、pが多いということは可能性は広がるのだが、mが奇数は可能性は低く、偶数であっても6の倍数は可能性は高いがそれ以外は低いのではなかろうかと予想している。
まぁ、予想は外れるくらいが面白いかもしれません。
さて、現実的に考えて、私のボロパソコンでどれくらいまで求められるだろうか。
仮に、m=10が1秒で終わったとしよう。
m=11は10倍かかるので10秒
m=12は11倍かかるので110秒
m=13は12倍かかるので1320秒=22分
m=14は13倍かかるので286分=4時間46分
m=15は14倍かかるので4004分=66時間44分
m=16は15倍かかるので60060分=1001時間=41日17時間
m=17は16倍かかるので16016時間=667日8時間
m=18は17倍かかるので272272時間=11344日16時間≒約31年ちょい
ああ、もうやめようw
新しいパソコン買おうかなぁ。
増税前に買ったほうがお得なのだろうか。
とりあえず今日はここまでとします。
実は既にプログラムは改善されており、m=11で10秒、m=13で110秒、m=14で18分といった感じで答えを出すまで速度アップしております。
みなさんも、nがいくつのとき解があるのか、あるとしたらどれくらいあるのかといった予想を立ててみてください。
ではでは