午後のひとときにプログラミングの問題を考えてみる。
問題
a!=b!*c!
x>a>b>c>1
を満たす、すべての解a, b, cを出力するプログラムを組め。
但し、
xは出来る限る大きいことが望ましく、出来る限り高速性が高いものを考えてください。
シンキングタ~イム
解答編です。
まずは数学的な考証からです。
n!とは
n!=n*(n-1)*(n-2)*…*1
ということです。
但し、
0!=1
と便宜上定義されています。
今回の問題は、a、b、cの大小関係を固定していることからも解るように、
1!=1!*0!
1!=1!*1!
2!=2!*1!
2!=2!*0!
…
といったものを排除していますね。
続いて、自明な解を考えます。
(n!)!=n!*(n!-1)!
これは階乗の定義から自明ですが、一応解説します。
m=n!とすると、
m!=m*(m-1)!
もう解りますよね。
では、自明ではない、非自明な解はというと、タイトルの
10!=7!*6!
こういう解があるということを踏まえて、プログラミングする必要があるということです。
さて、問題文の注意書きにもあるように、xを出来る限り大きく取りたい。
仮に、64ビット正整数だとすると、
最大値は、
18446744073709551615
これ以下のaを考えると、
20!=2432902008176640000
が限界となる。
これでは、
6!=5!*3!
の次の自明解
24!=23!*4!
すら表示せずに終わってしまい
自明解が1個と非自明解がタイトル以外に見つかればよいが、
おそらく見つからないだろう。
つまり、もっと頭を働かせる必要がある。
そこで登場するのが素因数分解。
素因数分解の指数だけを記録していけば、それほど大きな値にはならずに済む。
但し、最低でも素因数の数は配列が必要になってくる。
指数の配列も、素数だけに限定出来ればよいのだが、それをするには同じ大きさの素数の配列も必要になってしまう。
その辺りが肝になるだろうという感じで、プログラミングに取り掛かるとしよう。
mulfact1.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main()
{
unsigned int a, a2, b, b2, c, c2;
unsigned int i, j, k, p, s;
char m[1000000], n[1000000];
for (i=0; i<1000000; i++) m[i] = 0;
a2 = 6;
b2 = 1;
c2 = 3;
for (a=2, b=0; a<=2000000; a++) {
printf("%u!\r",a);
s = sqrtl(a)+1;
i = a;
p = 2;
if ( i%p == 0 && p > b ) b = p;
while ( i%p == 0 ) {
i /= p;
m[(p-1)/2]++;
}
for (p=3; p<s; p+=2) {
if ( i%p == 0 && p > b ) b = p;
while ( i%p == 0 ) {
i /= p;
m[(p-1)/2]++;
}
}
if ( i > 1 ) {
if ( i > b ) b = i;
m[(i-1)/2]++;
}
if ( a == b ) continue;
for (i=b2+1; i<=b; i++) {
s = sqrtl(i)+1;
j = i;
p = 2;
while ( j%p == 0 ) {
j /= p;
m[(p-1)/2]--;
}
for (p=3; p<s; p+=2) {
while ( j%p == 0 ) {
j /= p;
m[(p-1)/2]--;
}
}
if ( j > 1 ) {
m[(j-1)/2]--;
}
}
b2 = b;
if ( a == a2 ) {
printf("自明解: %u!=%u!*%u!\n",a2,a2-1,c2);
c2++;
a2 *= c2;
continue;
}
for (i=0; i<=(b-1)/2; i++) n[i] = m[i];
for (c=2; c<a; c++) {
s = sqrtl(c)+1;
i = c;
p = 2;
if ( i%p == 0 ) {
i /= p;
n[(p-1)/2]--;
}
if ( n[(p-1)/2] < 0 ) break;
for (p=3; p<s; p+=2) {
if ( i%p == 0 ) {
i /= p;
n[(p-1)/2]--;
}
if ( n[(p-1)/2] < 0 ) break;
}
if ( n[(p-1)/2] < 0 ) break;
if ( i > 1 ) {
n[(i-1)/2]--;
if ( n[(i-1)/2] < 0 ) break;
}
for (i=0; i<=(b-1)/2; i++) {
if ( n[i] != 0 ) break;
}
if ( n[i] == 0 ) {
printf("非自明解: %u!=%u!*%u!\n",a,b,c);
}
}
}
return EXIT_SUCCESS;
}
> mulfact1.exe
自明解: 6!=5!*3!
非自明解: 10!=7!*6!
自明解: 24!=23!*4!
自明解: 120!=119!*5!
自明解: 720!=719!*6!
自明解: 5040!=5039!*7!
自明解: 40320!=40319!*8!
自明解: 362880!=362879!*9!
2000000!
char型の1000000個の配列を2つ用意出来ましたので、
偶素数、奇素数として、奇数の素因数を半分に圧縮でき、
aを2000000まで探索出来ます。
mulfact2.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main()
{
unsigned int a, a2, b, b2, c, c2;
unsigned int i, j, k, p, q, s;
char m[1000001], n[1000001];
for (i=0; i<1000001; i++) m[i] = 0;
a2 = 6;
b2 = 1;
c2 = 3;
for (a=2, b=0; a<=3000000; a++) {
printf("%u!\r",a);
s = sqrtl(a)+1;
i = a;
p = 2;
if ( i%p == 0 && p > b ) b = p;
while ( i%p == 0 ) {
i /= p;
m[0]++;
}
p = 3;
if ( i%p == 0 && p > b ) b = p;
while ( i%p == 0 ) {
i /= p;
m[1]++;
}
for (p=5,q=2; p<s; p+=q,q=6-q) {
if ( i%p == 0 && p > b ) b = p;
while ( i%p == 0 ) {
i /= p;
m[(p+2)/3]++;
}
}
if ( i > 1 ) {
if ( i > b ) b = i;
m[(i+2)/3]++;
}
if ( a == b ) continue;
for (i=b2+1; i<=b; i++) {
s = sqrtl(i)+1;
j = i;
p = 2;
while ( j%p == 0 ) {
j /= p;
m[0]--;
}
p = 3;
while ( j%p == 0 ) {
j /= p;
m[1]--;
}
for (p=5,q=2; p<s; p+=q,q=6-q) {
while ( j%p == 0 ) {
j /= p;
m[(p+2)/3]--;
}
}
if ( j > 1 ) {
m[(j+2)/3]--;
}
}
b2 = b;
if ( a == a2 ) {
printf("自明解: %u!=%u!*%u!\n",a2,a2-1,c2);
c2++;
a2 *= c2;
continue;
}
for (i=0; i<=(b+2)/3; i++) n[i] = m[i];
for (c=2; c<a; c++) {
s = sqrtl(c)+1;
i = c;
p = 2;
while ( i%p == 0 ) {
i /= p;
n[0]--;
}
if ( n[0] < 0 ) break;
p = 3;
while ( i%p == 0 ) {
i /= p;
n[1]--;
}
if ( n[1] < 0 ) break;
for (p=5,q=2; p<s; p+=q,q=6-q) {
while ( i%p == 0 ) {
i /= p;
n[(p+2)/3]--;
}
if ( n[(p+2)/3] < 0 ) break;
}
if ( n[(p+2)/3] < 0 ) break;
if ( i > 1 ) {
n[(i+2)/3]--;
if ( n[(i+2)/3] < 0 ) break;
}
for (i=0; i<=(b+2)/3; i++) {
if ( n[i] != 0 ) break;
}
if ( n[i] == 0 ) {
printf("非自明解: %u!=%u!*%u!\n",a,b,c);
}
}
}
return EXIT_SUCCESS;
}
> mulfact2.exe
自明解: 6!=5!*3!
非自明解: 10!=7!*6!
自明解: 24!=23!*4!
自明解: 120!=119!*5!
自明解: 720!=719!*6!
自明解: 5040!=5039!*7!
自明解: 40320!=40319!*8!
自明解: 362880!=362879!*9!
3000000!
char型の1000001個の配列を2個確保出来ましたので、
2、3、6±1型素数として、素因数の個数を1/3+2に圧縮出来たので、
aを3000000まで探索出来るようになりました。
mulfact3.c
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
int main()
{
unsigned int a, a2, b, b2, c, c2;
unsigned int i, j, k, p, s;
char m[1000000], n[1000000];
char q[8] = {4,2,4,2,4,6,2,6};
char o[30] = {6,7,7,7,7,7,7,0,0,0,
0,1,1,2,2,2,2,3,3,4,
4,4,4,5,5,5,5,5,5,6};
for (i=0; i<1000000; i++) m[i] = 0;
a2 = 6;
b2 = 1;
c2 = 3;
for (a=2, b=0; a<=3750000; a++) {
printf("%u!\r",a);
s = sqrtl(a)+1;
i = a;
p = 2;
if ( i%p == 0 && p > b ) b = p;
while ( i%p == 0 ) {
i /= p;
m[0]++;
}
p = 3;
if ( i%p == 0 && p > b ) b = p;
while ( i%p == 0 ) {
i /= p;
m[1]++;
}
p = 5;
if ( i%p == 0 && p > b ) b = p;
while ( i%p == 0 ) {
i /= p;
m[2]++;
}
for (p=7,k=0; p<s; p+=q[k%8],k++) {
if ( i%p == 0 && p > b ) b = p;
while ( i%p == 0 ) {
i /= p;
m[k+3]++;
}
}
if ( i > 1 ) {
if ( i > b ) b = i;
m[(i-7)/30+o[i%30]+3]++;
}
if ( a == b ) continue;
for (i=b2+1; i<=b; i++) {
s = sqrtl(i)+1;
j = i;
p = 2;
while ( j%p == 0 ) {
j /= p;
m[0]--;
}
p = 3;
while ( j%p == 0 ) {
j /= p;
m[1]--;
}
p = 5;
while ( j%p == 0 ) {
j /= p;
m[2]--;
}
for (p=7,k=0; p<s; p+=q[k%8],k++) {
while ( j%p == 0 ) {
j /= p;
m[k+3]--;
}
}
if ( j > 1 ) {
m[(j-7)/30+o[j%30]+3]--;
}
}
b2 = b;
if ( a == a2 ) {
printf("自明解: %u!=%u!*%u!\n",a2,a2-1,c2);
c2++;
a2 *= c2;
continue;
}
for (i=0; i<=b*8/30+3; i++) n[i] = m[i];
for (c=2; c<a; c++) {
s = sqrtl(c)+1;
i = c;
p = 2;
while ( i%p == 0 ) {
i /= p;
n[0]--;
}
if ( n[0] < 0 ) break;
p = 3;
while ( i%p == 0 ) {
i /= p;
n[1]--;
}
if ( n[1] < 0 ) break;
p = 5;
while ( i%p == 0 ) {
i /= p;
n[2]--;
}
if ( n[2] < 0 ) break;
for (p=7,k=0; p<s; p+=q[k%8],k++) {
while ( i%p == 0 ) {
i /= p;
n[k+3]--;
}
if ( n[k+3] < 0 ) break;
}
if ( n[k+3] < 0 ) break;
if ( i > 1 ) {
n[(i-7)/30+o[i%30]+3]--;
if ( n[(i-7)/30+o[i%30]] < 0 ) break;
}
for (i=0; i<a; i++) {
if ( n[i] != 0 ) break;
}
if ( n[i] == 0 ) {
printf("非自明解: %u!=%u!*%u!\n",a,b,c);
}
}
}
return EXIT_SUCCESS;
}
> mulfact3.exe
自明解: 6!=5!*3!
非自明解: 10!=7!*6!
自明解: 24!=23!*4!
自明解: 120!=119!*5!
自明解: 720!=719!*6!
自明解: 5040!=5039!*7!
自明解: 40320!=40319!*8!
自明解: 362880!=362879!*9!
自明解: 3628800!=3628799!*10!
3750000!
char型の1000000個の配列を2個確保出来ましたので、
2、3、5、mod 30型素数として、素因数の個数を8/30+3に圧縮出来たので、
aを3750000まで探索出来るようになりました。
それによって、自明解が一つ多く表示されます。
結局、ここまで探索しても非自明解は、
10!=7!*6!
しか見つかっていません。
自明解は無限に存在することは証明出来ますが、
非自明解は無限に存在するのだろうか、
それとも有限個なのだろうか。
どうやら未解決問題のようです。
それにしても、このブログでは素因数分解が頻繁に出てきますね。
ではでは
knifeのmy Pick