C ++
ここで提示するのはアルゴリズムであり、3x3の場合の例で示されています。理論的にはNxNのケースに拡張できますが、それにははるかに強力なコンピューターや工夫が必要です。いくつかの改善点について説明します。
先に進む前に、数独グリッドの対称性、つまりささいな方法で別のグリッドにつながる変換に注目しましょう。ブロックサイズ3の場合、対称性は次のとおりです。
水平対称
**The N=3 sudoku is said to consist of 3 "bands" of 3 "rows" each**
permute the three bands: 3! permutations = 6
permute the rows in each band: 3 bands, 3! permutations each =(3!)^3=216
上下対称
**The N=3 sudoku is said to consist of 3 "stacks" of 3 "columns" each.**
the count is the same as for horizontal.
グリッドの水平反射と垂直反射は、これらの組み合わせによって実現できるため、カウントする必要がないことに注意してください。考慮すべきもう1つの空間対称性があります。これは転置であり、これはの係数です2
。これにより、全体の空間対称性が得られます
2*(N!*(N!)^N)^2 = 2*(6*216)^2=3359232 spatial symmetries for the case N=3.
次に、別の非常に重要な対称性があります。これは、再ラベル付けと呼ばれます。
Relabelling gives a further (N^2)!=9!=362880 symmetries for the case N=3. So the total
number of symmetries is 362880*3359232=1218998108160.
自己同型解の数が多い(1%未満)ため、対称性固有の解の数にこの数を掛けても、解の総数はわかりません。つまり、これらの特別なソリューションには、それらをそれら自体にマップする対称操作、または同じ他のソリューションにマップする複数の対称操作があります。
解の数を見積もるために、私は4つのステップで問題に取り組みます。
1.配列に0〜8のr[362880][12]
すべての可能な順列を入力します(これはプログラミングであり、Cで記述されているため、1〜9は使用しません)。鋭敏な場合は、2番目の添え字がわかります。これは12ではなく9です。これは、これを「行」と見なすことを念頭に置いてr[9,10,11] == 1<<a | 1<<b | 1<<c
、9、10、11が最初、2番目、3番目のスタックを参照する3つの整数も計算するためです。 a、b、cは、その行の各スタックに存在する3つの数値です。
2. b
3行のバンドのすべての可能な解で配列を埋めます。これを適度に小さく保つには、一番上の行が012,345,678であるソリューションのみを含めます。私はこれを力ずくで、可能なすべての中間の行を生成し、とのAND r[0][10,11,12]
をとることによってこれを行いますr[i][10,11,12]
。正の値は、同じ正方形に2つの同じ数値があり、バンドが無効であることを意味します。最初の2行に有効な組み合わせがある場合は、3番目(下)の行を同じ手法で検索します。
配列のサイズをb [2000000] [9]に設定しましたが、プログラムは1306368の解しか見つけません。いくつあるかわからなかったので、配列の次元はそのままにしておきました。これは実際には、シングルバンド(ウィキペディアで確認済み)の可能なソリューションの半分にすぎません。これは、現在の値から3行目だけをi
上向きにスキャンするためです。解の残りの半分は、2行目と3行目を交換することで簡単に見つけることができます。
情報が配列に格納される方法b
は、最初は少し混乱します。各整数を使用0..8
して特定の位置にある数値を格納する代わりに、ここで各整数は数値の1つを考慮し、0..8
それがどの列にあるかを示します。したがってb[x][7]==100100001
、解xの場合、列7が列0、5、8に(右から左に)見つかることを示します。この表現の理由は、ラベルを付け直してバンドの残りの可能性を生成する必要があるためです。表現はこれを行うのに便利です。
上記の2つのステップはセットアップを含み、約1分かかります(不要なデータ出力を削除した場合は、おそらく少なくなります。以下の2つのステップは実際の検索です)。
3衝突しない最初の2つのバンドの解をランダムに検索します(つまり、特定の列で同じ数が2回ない)。常に順列0と仮定して、バンド1のランダム解と、ランダムな順列。結果は通常9999回未満の試行で検出され(数千の範囲の最初のステージのヒット率)、1秒の数分の1です。順列によって、2番目のバンドについてはb []から解を取得することを意味します[]ここで、最初の行は常に012,345,678であり、最初の行の番号の可能なシーケンスが可能になるようにラベルを付け直します。
4ステップ3でヒットが見つかったら、他の2つと衝突しない3番目のバンドの解決策を検索します。1回だけ試行するのは望ましくありません。そうしないと、ステップ3の処理時間が無駄になります。一方、これに過度の労力を費やしたくない。
ちょうど楽しみのために、昨夜は可能な限り馬鹿げた方法でそれを行いましたが、それはまだ興味深いものでした(何年もの間何もなかったため、大量のソリューションが一気に見つかりました)。これが有効なソリューションではないことがわかったらすぐに(!z)
最後のk
ループを中止しました(これにより、実行速度が約9倍速くなります)。ブロック、合計474054819840の可能性。これは、第2ステージのヒット率が400000分の1です。スキャンではなくランダム検索ですぐに再試行します。ほんの数秒で、数百万回の試行で妥当な答えが得られるはずです。
全体的な答えは、(362880 *(1306368 * 2))^ 3 *ヒット率= 8.5E35 *ヒット率になるはずです。質問の数値から逆算すると、ヒット率は1 / 1.2E14になると思います。私の単一のデータポイントでこれまでに得たものは1 /(400000 * 1000)で、これは約100万倍のアウトです。これは、偶然の異常、プログラムのエラー、または数学のエラーの可能性があります。あと何回かテストを実行するまで、それがどちらなのかわかりません。
これを今夜ここに置いておきます。テキストは少しスクラップです。すぐに片付けます。うまくいけば、さらに結果を追加します。また、より速くする方法と、概念をN = 4に拡張する方法について少し説明します。私はプログラムにあまり多くの変更を加えることはないと思いますが、:-)
ああ..プログラム:
#include "stdafx.h"
#define _CRT_RAND_S
#include <algorithm>
#include <time.h>
unsigned int n[] = { 0,1,2,3,4,5,6,7,8 }, r[362880][12], b[2000000][9],i,j,k,l,u,v,w,x,y,z;
int main () {
//Run through all possible permutations of n[] and load them into r[][]
i=0;
do {
r[i][9] = r[i][10] = r[i][11]=0;
for (l = 0; l < 9; l++){
r[i][l] = n[l];
r[i][9 + l / 3] |= 1 << n[l];
}
if((i+1)%5040==0) printf("%d%d%d %d%d%d %d%d%d %o %o %o %o \n"
,r[i][0],r[i][1],r[i][2],r[i][3],r[i][4],r[i][5],r[i][6],r[i][7],r[i][8],r[i][9],r[i][10],r[i][11],r[i][9]+r[i][10]+r[i][11]);
i++;
} while ( std::next_permutation(n,n+9) );
//Initialise b[][]
for (l = 0; l<2000000; l++) for (k = 0; k<9; k++) b[l][k]=0;
//fill b[][] with all solutions of the first band, where row0 ={0,1,2,3,4,5,6,7,8} and row1<row2
l=0;
for (i = 0; i<362880; i++)
if (!(r[0][9] & r[i][9] | r[0][10] & r[i][10] | r[0][11] & r[i][11])){printf("%d %d \n",i,l);
for (j=i; j<362880;j++)
if(!(r[0][9]&r[j][9] | r[0][10]&r[j][10] | r[0][11]&r[j][11] | r[j][9]&r[i][9] | r[j][10]&r[i][10] | r[j][11]&r[i][11] )){
for (k = 0; k < 9; k++){
b[l][r[0][k]]|=1<<k;
b[l][r[i][k]]|=1<<k;
b[l][r[j][k]]|=1<<k;
}
l++;
}
// printf("%d%d%d %d%d%d %d%d%d %o %o %o %o \n"
// ,r[i][0],r[i][1],r[i][2],r[i][3],r[i][4],r[i][5],r[i][6],r[i][7],r[i][8],r[i][9],r[i][10],r[i][11],r[i][9]+r[i][10]+r[i][11]);
// printf("%d%d%d %d%d%d %d%d%d %o %o %o %o \n"
// ,r[j][0],r[j][1],r[j][2],r[j][3],r[j][4],r[j][5],r[j][6],r[j][7],r[j][8],r[j][9],r[j][10],r[j][11],r[j][9]+r[j][10]+r[j][11]);
// printf("%d %d %o %o %o %o %o %o %o %o %o \n",i,l,b[l][0],b[l][1],b[l][2],b[l][3],b[l][4],b[l][5],b[l][6],b[l][7],b[l][8]);
}
// find a random solution for the first 2 bands
l=0;
do{
rand_s(&u); u /= INT_MIN / -653184; //1st band selection
rand_s(&v); v /= INT_MIN / -181440; //2nd band permutation
rand_s(&w); w /= INT_MIN / -653184; //2nd band selection
z = 0;
for (k = 0; k < 9; k++) z |= b[u][k] & b[w][r[v][k]];
l++;
} while (z);
printf("finished random after %d tries \n",l);
printf("found solution with top band %d permutation 0, and middle band %d permutation %d \n",u,w,v);
getchar();
// scan all possibilities for the last band
l=0;
for (i = 0; i < 362880; i++) for (j = 0; j < 1306368; j++){
z=0;
for(k=0;(k<9)&&(!z);k++) z|= b[u][k] & b[j][r[i][k]] | b[j][r[i][k]] & b[w][r[v][k]];
if (!z){ l++; printf("solution %d : i= %d j=%d",l,i,j); }
}
printf("finished bottom band scan at %d millisec \n", clock()); getchar();
}