有多少个数独谜题?


10

这不是Sudoku求解器,也不是Sudoku检查器。

您的挑战是编写一个函数或脚本,作为输入,它提供2D Sudoku拼图的“块”大小(对于经典9x9板为3 ,对于16x16板为4 ,等等),将计算出该数字的近似值针对该大小存在的不同难题(解决方案)。

例如,在给定输入3的情况下,您的程序应该以期望的精度打印数字6,670,903,752,021,072,936,960的近似值,这是已知的9x9 Sudoku数独难题已知数量,或者在考虑到各种对称性时为5,472,730,538。您的解决方案应说明是计算对称性还是忽略对称性。

“所需的精度”没有定义:您的程序可能运行给定的时间,然后输出结果,或者将其计算到给定数量的有效数字,甚至永远运行,从而打印出越来越好的近似值。关键是应该有可能使其在有限的时间内以任何所需的精度计算结果。(因此,“ 42”是不可接受的答案。)将结果的精度限制在可用的机器浮动范围内是可以接受的。

不能访问在线资源,不能在文件名中存储源代码,等等。


PS:我知道这是一个难题(如果我没记错的话,请填写NP。)但是这个问题只是在寻求一个近似的统计解决方案。例如,您可以尝试满足一个(或两个以上)约束的随机配置,计算其中存在多少约束,然后检查获得满足所有三个约束的难题的频率。对于小尺寸(确定为size = 3,可能为4),这将在适当的时间内起作用,但是该算法应该足够通用,以适合任何尺寸。

最好的算法获胜。


PS2:我从“代码高尔夫”更改为“代码挑战”,以更好地反映问题的难度并鼓励采用更聪明的解决方案,而不是笨拙但效果良好的解决方案。但是由于显然“最佳算法”尚不清楚,所以让我尝试正确定义它。

给定足够的时间并且不考虑常量因素(包括CPU和解释器速度),或者等效地考虑它们的无症状行为,哪种解决方案将以最快的速度收敛到准确的结果?


11
这不是真的很 困难的 问题吗?您是否只是在寻求产生函数以产生数字{1、1、288、6e21}的最短方法,还是将其扩展到n> 3?
algorithmhark

确切的解决方案是一个难以置信的难题,但是可以通过一些随机采样和几秒钟的现代CPU时间来计算近似值。当然,欢迎使用更智能的解决方案!
Tobia 2014年

2
@Tobia此方法用于查找需要N次移动才能解决kociemba.org/cube.htm的魔方位置的近似数量,因此可以通过这种方式获得近似值。但是,如果我编写一个程序使每一行都求解,然后进行测试以查看列和正方形是否求解,那么它将有(9!)^ 9 = 1E50种蛮力攻击的可能性,其中只有6E21被击中(根据问题。)平均每个匹配需要1.6E28次尝试。那太慢了。现在,如果我可以确保行和列都正确并且仅检查正方形,那我就到了。啊! 我有个主意...
Level River St'St

@steveverrill看到了吗?:-)
Tobia

没有分析解决方案吗?
Newbrict 2014年

Answers:


3

C ++

我将在此处介绍的是一种算法,并以3x3的情况为例进行说明。从理论上讲,它可以扩展到NxN情况,但是这将需要功能更强大的计算机和/或一些巧妙的调整。我会在介绍过程中提到一些改进。

在继续之前,让我们注意Sudoku网格的对称性,即以平凡的方式导致另一个网格的转换。对于块大小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.

注意,网格的水平和垂直反射可以通过结合使用这些方法来实现,因此不需要计算它们。还有一个空间对称要考虑,它是转置的,这是的因子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%)自同构解。这意味着对于这些特殊解决方案,有一个将它们映射到自身的对称操作,或者有多个将它们映射到同一其他解决方案的对称操作。

为了估算解决方案的数量,我分四个步骤解决了这个问题:

1. r[362880][12]用数字0到8的所有可能排列填充数组(这是编程的,它在C中,因此我们不打算使用1到9。)如果您比较聪明,则会注意到第二个下标是12而不是9。这是因为在执行此操作时,请记住我们将其视为“行”,因此我们还要计算另外三个整数r[9,10,11] == 1<<a | 1<<b | 1<<c,其中9,10,11分别指向第一,第二和第三堆栈a,b,c是该行的每个堆栈中存在的三个数字。

2 b用3行带的所有可能解填充数组。为了使此值保持在合理范围内,请仅包括顶行为012,345,678的解决方案。我通过生成所有可能的中间行并r[0][10,11,12]与进行与运算,以蛮力实现r[i][10,11,12]。任何正值表示在同一平方中有两个相同的数字,并且该范围无效。当前两行有有效的组合时,我使用相同的技术搜索第三行(底部)。

我将数组的尺寸标注为b [2000000] [9],但该程序仅找到1306368解决方案。我不知道有多少个,所以我就这样离开了数组。实际上,这只是单个频段(在Wikipedia上验证)的可能解决方案的一半,因为我仅从当前值i向上扫描第三行。通过交换第二行和第三行,可以轻松找到其余一半的解决方案。

首先,将信息存储在数组b中的方式有​​些混乱。而不是使用每个整数存储0..8在给定位置找到的数字,这里每个整数都考虑一个数字,0..8并指出可以在哪些列中找到它。因此,b[x][7]==100100001这表示对于解决方案x,在第0,5和8列(从右到左)中找到了数字7。这种表示的原因是,我们需要通过重新标记来生成频段的其余可能性,并且表示法使执行此操作变得很方便。

上面的两个步骤包括设置过程,大约需要一分钟(如果我删除了不必要的数据输出,则可能会少一些。下面的两个步骤是实际的搜索。)

3随机搜索前两个不冲突的条带的解(即在给定列中没有两次相同的数字。我们选择条带1的一个随机解,假设总置换为0,而条带2的一个随机解为随机排列,通常少于9999次尝试(第一阶段的命中率在数千范围内),并需要一秒钟的时间。通过排列,我的意思是,对于第二个波段,我们从b []中得到一个解。 [],其中第一行始终为012,345,678,并重新标记它,以便第一行上的任何可能的数字序列都是可能的。

4在步骤3中找到一个命中点后,为不与其他两个片段冲突的第三个片段寻找解决方案。我们不想只尝试一次,否则将浪费步骤3的处理时间。另一方面,我们不想为此付出过多的努力。

只是为了好玩,昨晚我以最愚蠢的方式做到了,但它仍然很有趣(因为很久以来它一无所获,所以突然发现了大量解决方案。)即使花费了一点点黑客,整夜都花了一个数据点一旦知道这不是一个有效的解决方案,(!z)我就终止了最后一个k循环(这使其运行速度快了将近9倍。)在搜索了所有1306368标准解决方案的所有362880重新标记后,它为整个网格找到了1186585个解决方案区块,总共有474054819840个可能性。第二阶段的命中率为4百万分之一。我将很快尝试使用随机搜索而不是扫描。只需几百万次尝试,它就应该给出一个合理的答案,而这只需几秒钟。

总体答案应为(362880 *(1306368 * 2))^ 3 *命中率= 8.5E35 *命中率。通过从问题中的数字进行回算,我预计命中率为1 / 1.2E14。到目前为止,我对单个数据点的了解是1 /(400000 * 1000),相差约一百万。这可能是偶然的异常,程序错误或数学错误。在运行更多测试之前,我不知道它是什么。

我今晚将其留在这里。文本有点草率,我会尽快整理一下,希望能再增加一些结果,也许还会谈几句话,说明如何使其更快以及如何将概念扩展到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();
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.