随分と日数が経ってしまいましたが、プログラミング編です。
図のような0から9を模した7セグメントっぽいピースと枠がある。
枠内に、全てのピースを重ならないように詰めて下さい。
ピースは回転しても裏返しても構いません。
それぞれのサイズは、方眼の通りです。
シンキングタ~イム
綺麗(すべてがメッシュ状に収まる)な解だけを探すプログラムを組むことができました。
プログラミングと一口に言っても、今回はプログラムを組むことよりも、大変な作業がありました。
それはデータを作ること。
unsigned long long num[2][40] = {{
0x9fcf0f80bf0f3f6f, /* 縦0 */
0x9fcf0fe02f0f3f6f,
0x9fcf0fe08f0f3f60,
0x9fcf0f20bf0f3f60,
0x00000000008fef25, /* 縦1 */
0x00000000008fbf25,
0x00000000008fbf20,
0x00000000008fef20,
0x1fc00f9f6f003f4f, /* 縦2 */
0x1fc00f9f6f003f40,
0x9f4f003fc00f1f6f,
0x9f4f003fc00f1f60,
0x9f4f00bf4f003f4f, /* 縦3 */
0x1fc00f1fe00f1f6f,
0x1fc00f1fe00f1f60,
0x9f4f00bf4f003f40,
0x800f00bfcf0f202f, /* 縦4 */
0x808f0f3fe00f002f,
0x00800f9fef0f202f,
0x808f0fbf6f00200f,
0x9f4f003fc00f1f6f, /* 縦5 */
0x9f4f003fc00f1f60,
0x1fc00f9f6f003f4f,
0x1fc00f9f6f003f40,
0x9fcf0f3fe00f1f6f, /* 縦6 */
0x9f4f00bfcf0f3f6f,
0x9fcf0fbf6f003f4f,
0x1fc00f9fef0f3f6f,
0x800f00e00f003f4f, /* 縦7 */
0x1fc00f00b00f002f,
0x00800f00b00f1f6f,
0x9f4f00e00f00200f,
0x9fcf0fbfef0f3f6f, /* 縦8 */
0x9fcf0fbfef0f3f60,
0x9fcf0fbfef0f3f60,
0x9fcf0fbfef0f3f60,
0x9f4f00bfcf0f3f6f, /* 縦9 */
0x9fcf0f3fe00f1f6f,
0x1fc00f9fef0f3f6f,
0x9fcf0fbf6f003f4f},{
0x9f4fcf000f3fdf6f, /* 横0 */
0x9f7fcf000f3f1f6f,
0x9f1fcf000f3fdf60,
0x9f7fcf000f3f4f60,
0x00000000001f7f45, /* 横1 */
0x00000000001fdf45,
0x00000000001fdf40,
0x00000000001f7f40,
0x9fc08f0f0f203f6f, /* 横2 */
0x9fc08f0f0f203f60,
0x809fcf0f0f3f602f,
0x809fcf0f0f3f6020,
0x9fdfcf0f0f20202f, /* 横3 */
0x80808f0f0f3f7f6f,
0x80808f0f0f3f7f60,
0x9fdfcf0f0f202020,
0x1fdf400f001f600f, /* 横4 */
0x009f400f001f7f4f,
0x1fc0000f001f7f4f,
0x1fdf400f00003f4f,
0x809fcf0f0f3f602f, /* 横5 */
0x809fcf0f0f3f6020,
0x9fc08f0f0f203f6f,
0x9fc08f0f0f203f60,
0x809fcf0f0f3f7f6f, /* 横6 */
0x9fdfcf0f0f3f602f,
0x9fdfcf0f0f203f6f,
0x9fc08f0f0f3f7f6f,
0x9f7f4f000020000f, /* 横7 */
0x000080000f1fdf6f,
0x80000f00003fdf4f,
0x1f7fc0000f00002f,
0x9fdfcf0f0f3f7f6f, /* 横8 */
0x9fdfcf0f0f3f7f60,
0x9fdfcf0f0f3f7f60,
0x9fdfcf0f0f3f7f60,
0x9fdfcf0f0f3f602f, /* 横9 */
0x809fcf0f0f3f7f6f,
0x9fc08f0f0f3f7f6f,
0x9fdfcf0f0f203f6f}};
画像の2×2の4マスを正方形の1ブロックとみて、2本の対角線で分割し、反時計回りに9時の方角から、1、2、4、8とした。
全部埋めれば16進数でfとなる。
1つのピースは1を除けば3×5の15ブロックで、16進数15桁で表すことが可能となる。
最後の1桁は使うブロック数としましたので、unsigned long long型で間に合いました。
1つのピースは最大で縦で4パターン、横で4パターンの8パターンの可能性があり、全てデータとして用意しました。
但し、対称性があって既に調べたものを調べ直すのは馬鹿らしいので、最後の桁を0として、データは存在するが利用しないというようなフラグを立てました。
このデータ作りに何日か費やしました。
データの入力ミスは、コンパイラには解りませんので、出来上がったデータは、形として出力させて、正しいかを確認する作業が続きます。
データが完成してしまえば、後はコーディングだけです。
このデータの構造にしようと決めたのは、これならばANDを取れば当たり判定が容易だと考えたからです。
多重ネストになるのは、ちょっと嫌だったので、再帰処理にしました。
再帰処理
unsigned char map[9][11];
int l[10];
int X[10],Y[10],V[10],R[10];
int put_tate(int n, int m, int x, int y)
{
int i,j,f;
unsigned long long buf1,buf2;
buf1 = num[0][4*n+m];
f = buf1%16;
buf1 /= 16;
buf2 = buf1;
if ( f == 0 ) return 0;
for (j=0; j<5; j++)
for (i=0; i<f/5; i++) {
if ( x+i > 8 || y+j > 10 || ( map[x+i][y+j] & buf1%16 ) > 0 ) return 0;
buf1 /= 16;
}
for (j=0; j<5; j++)
for (i=0; i<f/5; i++) {
map[x+i][y+j] += buf2%16;
buf2 /= 16;
}
X[n]=x;
Y[n]=y;
R[n]=m;
V[n]=0;
return 1;
}
int put_yoko(int n, int m, int x, int y)
{
int i,j,f;
unsigned long long buf1,buf2;
buf1 = num[1][4*n+m];
f = buf1%16;
buf1 /= 16;
buf2 = buf1;
if ( f == 0 ) return 0;
for (j=0; j<f/5; j++)
for (i=0; i<5; i++) {
if ( x+i > 8 || y+j > 10 || ( map[x+i][y+j] & buf1%16 ) > 0 ) return 0;
buf1 /= 16;
}
for (j=0; j<f/5; j++)
for (i=0; i<5; i++) {
map[x+i][y+j] += buf2%16;
buf2 /= 16;
}
X[n]=x;
Y[n]=y;
R[n]=m;
V[n]=1;
return 1;
}
int clear_map()
{
int i,j;
for (j=0; j<11; j++)
for (i=0; i<9; i++) map[i][j] = 0;
return 1;
}
int save_map(char *buf)
{
int i;
for (i=0; i<99; i++) buf[i] = map[i/11][i%11];
return 0;
}
int load_map(char *buf)
{
int i;
for (i=0; i<99; i++) map[i/11][i%11] = buf[i];
return 0;
}
int next_number(int n)
{
int x,y,r,f,i;
char buf[99];
if ( n < 10 ) {
save_map(buf);
for (x=0; x<9; x+=2)
for (y=0; y<7; y+=2)
for (r=0; r<4; r++) {
f = put_tate(l[n],r,x,y);
if ( f == 1 ) {
printf("%1dT%02d%02d%1d ",l[n],x,y,r);
next_number(n+1);
load_map(buf);
printf("\b\b\b\b\b\b\b\b");
}
}
for (x=0; x<5; x+=2)
for (y=0; y<11; y+=2)
for (r=0; r<4; r++) {
f = put_yoko(l[n],r,x,y);
if ( f == 1 ) {
printf("%1dY%02d%02d%1d ",l[n],x,y,r);
next_number(n+1);
load_map(buf);
printf("\b\b\b\b\b\b\b\b");
}
}
printf(" \b\b\b\b\b\b\b\b");
} else {
printf("\r");
for (i=0; i<10; i++) printf("%d=(%d,%d,%d,%d) ",i,X[i],Y[i],V[i],R[i]);
printf("\n");
return 0;
}
}
後は、事前に解っている情報があれば、それを固定して、next_number()関数を必要なところから始めれば良い。
例えば、事前情報が一切ないならば、
int main()
{
l[0]=0,l[1]=1,l[2]=2,l[3]=3,l[4]=4,l[5]=5,l[6]=6,l[7]=7,l[8]=8,l[9]=9;
next_number(0);
return EXIT_SUCCESS;
}
と、置くピースの順番(配列l)だけ指定して、0番目からすれば良い。
自分は、0は、必ず1か7と組み合わせて、枠内の縁に接する必要があることを考慮して、
int main()
{
l[0]=0,l[1]=1,l[2]=2,l[3]=3,l[4]=4,l[5]=5,l[6]=6,l[7]=7,l[8]=8,l[9]=9;
printf("CHECK #1\n");
clear_map();
put_tate(0,0,0,0);
put_yoko(1,0,0,2);
next_number(2);
printf("CHECK #2\n");
clear_map();
put_tate(0,0,0,2);
put_yoko(1,0,0,4);
next_number(2);
printf("CHECK #3\n");
clear_map();
put_tate(0,0,0,4);
put_yoko(1,0,0,6);
next_number(2);
printf("CHECK #4\n");
clear_map();
put_tate(0,0,0,6);
put_yoko(1,0,0,8);
next_number(2);
printf("CHECK #5\n");
clear_map();
put_yoko(0,1,0,8);
put_tate(1,0,2,6);
next_number(2);
printf("CHECK #6\n");
clear_map();
put_yoko(0,1,2,8);
put_tate(1,0,4,6);
next_number(2);
printf("CHECK #7\n");
clear_map();
put_yoko(0,1,4,8);
put_tate(1,0,6,6);
next_number(2);
l[0]=0,l[1]=7,l[2]=1,l[3]=2,l[4]=3,l[5]=4,l[6]=5,l[7]=6,l[8]=8,l[9]=9;
printf("CHECK #8\n");
clear_map();
put_tate(0,0,0,0);
put_yoko(7,0,0,0);
put_yoko(1,1,2,0);
next_number(3);
printf("CHECK #9\n");
clear_map();
put_tate(0,0,0,2);
put_yoko(7,0,0,2);
next_number(2);
printf("CHECK #10\n");
clear_map();
put_tate(0,0,0,4);
put_yoko(7,0,0,4);
next_number(2);
printf("CHECK #11\n");
clear_map();
put_tate(0,0,0,6);
put_yoko(7,0,0,6);
next_number(2);
printf("CHECK #12\n");
clear_map();
put_yoko(0,1,0,8);
put_tate(7,0,0,6);
put_tate(1,1,0,4);
next_number(3);
printf("CHECK #13\n");
clear_map();
put_yoko(0,1,2,8);
put_tate(7,0,2,6);
next_number(2);
printf("CHECK #14\n");
clear_map();
put_yoko(0,1,4,8);
put_tate(7,0,4,6);
next_number(2);
return EXIT_SUCCESS;
}
CHECK #1からCHECK #14までで、0の場所を変えながら、それを検索させました。
結果は、
CHECK #1
CHECK #2
CHECK #3
CHECK #4
CHECK #5
CHECK #6
CHECK #7
CHECK #8
CHECK #9
CHECK #10
CHECK #11
CHECK #12
0=(0,8,1,1) 1=(0,4,0,1) 2=(2,0,0,2) 3=(6,6,0,0) 4=(6,2,0,0) 5=(2,4,1,0) 6=(0,0,0,0) 7=(0,6,0,0) 8=(4,0,1,0) 9=(4,6,0,0)
0=(0,8,1,1) 1=(0,4,0,1) 2=(2,0,0,2) 3=(6,6,0,0) 4=(6,2,0,0) 5=(2,4,1,0) 6=(4,6,0,1) 7=(0,6,0,0) 8=(4,0,1,0) 9=(0,0,0,1)
0=(0,8,1,1) 1=(0,4,0,1) 2=(4,6,0,2) 3=(0,0,0,1) 4=(6,2,0,0) 5=(2,4,1,0) 6=(2,0,0,0) 7=(0,6,0,0) 8=(4,0,1,0) 9=(6,6,0,0)
0=(0,8,1,1) 1=(0,4,0,1) 2=(4,6,0,2) 3=(0,0,0,1) 4=(6,2,0,0) 5=(2,4,1,0) 6=(6,6,0,1) 7=(0,6,0,0) 8=(4,0,1,0) 9=(2,0,0,1)
0=(0,8,1,1) 1=(0,4,0,1) 2=(2,4,1,2) 3=(0,0,0,1) 4=(6,2,0,0) 5=(4,6,0,0) 6=(2,0,0,0) 7=(0,6,0,0) 8=(4,0,1,0) 9=(6,6,0,0)
0=(0,8,1,1) 1=(0,4,0,1) 2=(2,4,1,2) 3=(0,0,0,1) 4=(6,2,0,0) 5=(4,6,0,0) 6=(6,6,0,1) 7=(0,6,0,0) 8=(4,0,1,0) 9=(2,0,0,1)
0=(0,8,1,1) 1=(0,4,0,1) 2=(2,4,1,2) 3=(6,6,0,0) 4=(6,2,0,0) 5=(2,0,0,0) 6=(0,0,0,0) 7=(0,6,0,0) 8=(4,0,1,0) 9=(4,6,0,0)
0=(0,8,1,1) 1=(0,4,0,1) 2=(2,4,1,2) 3=(6,6,0,0) 4=(6,2,0,0) 5=(2,0,0,0) 6=(4,6,0,1) 7=(0,6,0,0) 8=(4,0,1,0) 9=(0,0,0,1)
CHECK #13
0=(2,8,1,1) 1=(8,4,0,0) 2=(6,6,0,2) 3=(6,0,0,0) 4=(0,2,0,2) 5=(2,4,1,2) 6=(4,0,0,2) 7=(2,6,0,0) 8=(0,0,1,0) 9=(0,6,0,2)
0=(2,8,1,1) 1=(8,4,0,0) 2=(6,6,0,2) 3=(6,0,0,0) 4=(0,2,0,2) 5=(2,4,1,2) 6=(0,6,0,3) 7=(2,6,0,0) 8=(0,0,1,0) 9=(4,0,0,3)
0=(2,8,1,1) 1=(8,4,0,0) 2=(2,4,1,0) 3=(6,0,0,0) 4=(0,2,0,2) 5=(6,6,0,0) 6=(4,0,0,2) 7=(2,6,0,0) 8=(0,0,1,0) 9=(0,6,0,2)
0=(2,8,1,1) 1=(8,4,0,0) 2=(2,4,1,0) 3=(6,0,0,0) 4=(0,2,0,2) 5=(6,6,0,0) 6=(0,6,0,3) 7=(2,6,0,0) 8=(0,0,1,0) 9=(4,0,0,3)
CHECK #14
0を縦向きで置いたケース、#1から#7までに、解は一つも見つからず、
0を横向きで置いて、7を結合させたケースだけで、解が12通り、
但し、2と5、6と9、それぞれの入れ替えを含む解が見つかる。
もし、0の上下、裏表を固定しなければ、トータル48通りの解があったということです。
2と5、6と9、上下、裏表、これらの入れ替えを同じものと考えるならば、解はたったの3通りしかないということでもあります。
画像自体は、HTML5とJavascriptで描いています。
ただ、このプログラムの構造上、綺麗にメッシュ状になる解しか探索しませんので、それ以外の解は見つけてくれません。
もし、それらまで見つけるようなものを書くと、
ピースの面積を4倍にする。
探索する範囲は同じだが、メッシュ状以外も探索させるため、量が4倍になる。
また、0の場所は固定したとしても、1や7をはめ込まない解も存在する可能性があるため、事前の枝刈りがほぼ出来ない。
1つ場所を固定すると単純に10倍速くなると考えることが出来る。
2つ場所を固定すると100倍、3つ場所を固定すると1000倍という感じです。
メッシュ以外も探索させるとなると、ピースの構造上16倍遅くなり、更に枝刈りが全くといっていいほど出来ないので100倍で、プログラム的に1600倍くらい時間が掛かる単純計算となってしまう。
さて、メッシュ状の解の個数が解ったので、このパズルの難しさを考えてみる。
あくまでも単純計算ね。
10ピースあるので、場所は、10!=3628800
向きが4、表裏が2で、10^8=100000000
掛け合わせて、362880000000000
母数が362兆8800億通りだとすると、解は3×16=48
7兆56億分の1の難しさなのだろうか。
数学的に、横向きが3ピース、残りの7ピースが縦向きとなることが解りましたね。
横向きの3ピースを固定すると18通りだけで、
横向きの3ピースの場所は、3!=6
残り7ピースの場所は、7!=5040
向きが2、裏表が2で、10^4=10000
掛け合わせて、302400000
3億240万分の48、
630万分の1の難しさになりました。
3種類の解を眺めてみると、8、4、2または5の3つのピースの位置関係が固定されていることに気が付きますね。
こういう情報があれば、難しさは格段に減っていくこととなるだろう。
ではでは
knifeのmy Pick