原文题目链接:
http://projecteuler.net/problem=96
翻译题目链接:
http://pe.spiritzhang.com/index.php/2011-05-11-09-44-54/97-96
通过人数:8333
题目分析:
哇塞,居然是数独~ 数独高手最鄙视用假设用搜索了~ 所以我才不要用搜索...于是可怜的博主走上了一条不归路...
【如果不想走上不归路的童鞋我可以告诉你这题答案是24702然后博主推荐你去看网上的一篇博客:
http://simplesource.blog.163.com/blog/static/1034140620077104021963/
我没有细看这篇博客不过感觉上挺靠谱的~用这种算法解速度又快代码又短~】
然后数独要怎么解呢~
我参考了百度数独吧的数独教程,不知道数独是什么或者对数独不太了解的童鞋也可以过去学习一下。
完整代码可读性不强,其实感觉应该用java面向对象写的,但我刚自学java不到1个星期,还不太熟悉...于是就写成了这样的c风格的C++代码。我将完整代码放在最后。
并且其实这50个数独我并没有都解出来,有48个数独不用搜索完全解出来了,有两个没有。这两个没有解出来的左上角的三位数各有两种可能性,于是我就把一共4种可能性在网站上试了一下,得到了最后的答案。30s一次,还是挺划算的。
这个代码以后有比较大的修改提升的余地。我以后可能会考虑跳出这道题的框架修改之后再写文章的。
废话少说,接下来说一下我的解法。
解题过程
一、关于代码结构的说明:
代码主要就是一个类,然后利用文件读入输入信息,并将输入信息转化成二位整型数组来初始化这个类的对象。
属性的含义如下:
int num[9][9];
// 储存数独中目前已经填出来的数,未填出的以0补
int b_num[9][9];
//标注这一位都可以填一些什么数,用二进制表示的后9位标注9~1是否可填入。如果是已填出的格子则二进制只有一位为1
int n_num[9][9];
//标注这一位可以填多少个数。如果是已填出的格子则为0
int s_n;
//表示一共有多少个格子被填出了。本来是想实时更新的,但写的时候忘了,于是就写了一个函数bool CountS()来专门计算这个值并考察其是否为81
在初始化之后用不超过100遍的下面讲的方法进行求解,若解出则计算题目所求值存在r_ans中。ans统计有多少个未解出的数独并会将每一个未解出的数独输出。
二、首先,我实现了“唯余法”(具体什么是唯余法详见上面的教程链接)
主要用了以下两个函数
void Checkpoint(int x, int y)
void Checkallpoint()
前者是对某一个点进行排除。若这个点是未填出的点则报错,否则对同行、列、宫中的格子进行排除。
如果在排除的时候发现有的点已经能填出了,就将这个点填出并也对其进行排除。
利用stl队列实现的。
后者是对所有填出的点调用前者的函数。
仅用唯余法就已经解除了第一个输入的数独。
三、其次,我实现了“排除法”的前一部分(具体什么是排除法详见上面的教程链接)
主要用了以下四个函数
void Checklinei(int l)
//行排除法
void Checklinej(int l)
//列排除法
void Checkblock(int x,int y)
//宫内排除法
void Checkallnum()
//逐行、逐列、逐宫地调用以上三个函数。
在排除出某一个点的值可填出的时候调用前面提到的函数 Checkpoint(int x, int y)对其周围的点进行检查。
仅重复运用唯余法和排除法的前一部分就已经解出了这50个输入的数独中的40个。
四、再次,我实现了“排除法”的其余部分(具体什么是排除法详见上面的教程链接)
主要用了以下两个函数
void Checkblockline(int x, int y)
//对宫x,y进行区块排除法。
void Checkblocklineall()
//对每一个区块调用前面的那个函数。
仅重复运用唯余法和排除法就已经解出了这50个输入的数独中的43个。
五、最后,我实现了"数对占位法"中的二元的部分(具体什么是数对占位法详见上面的教程链接)
之所以只实现了二元的部分因为发现多元的情况本身出现的概率就极低,然后效果还不好~
曾经实现了一下三元的,然后发现毫无附加效果。于是一怒之下把三元的给删掉了~
主要用了以下四个函数
void Checkpairlinei(int l)
//函数功能 检查第l行中的数对情况并标记
void Checkpairlinej(int l)
//函数功能 检查第l列中的数对情况并标记
void Checkpairblock(int x, int y)
//函数功能 检查第x,y块中的数对情况并标记
void Checkpairall()
//逐行、逐列、逐宫地调用以上三个函数。
这段代码我还是写了一些注释的,因为复杂到我发现我不写注释自己都看不太懂了...有兴趣的可以到文章末尾的完整代码中找来看看。
仅重复运用唯余法和排除法和2元数对站位法就已经解出了这50个输入的数独中的48个。
六、最后的最后,函数运行会输出两个未解出的中间结果:
0 4 3 9 8 0 2 5 0
6 0 0 4 2 5 0 0 0
2 0 0 0 0 1 0 9 4
9 0 0 0 0 4 0 7 0
3 0 0 6 0 8 0 0 0
4 1 0 2 0 9 0 0 3
8 2 0 5 0 0 0 0 0
0 0 0 0 0 0 0 0 5
5 3 4 8 9 0 7 1 0
左上角的数为1
8 0 1 0 0 7 0 9 0
5 9 0 0 8 0 0 6 1
0 3 0 0 0 0 0 8 0
0 1 0 2 7 5 8 4 3
3 5 8 0 6 0 1 2 7
2 7 4 1 3 8 9 5 6
0 8 0 0 0 0 0 3 0
1 0 0 8 2 0 0 7 9
0 2 0 7 0 0 4 1 8
左上角再偏右一格的数是6
这最后两个解题需要的数都只有两种可能,所以一共是四种可能,于是我就上projecteuler尝试地提交了一下,试出了结果:24702
有机会的话一定要对这个程序进行改进。用这个做数独游戏可以设计提示信息哦~还可以利用这种思路对比较简单的数独游戏进行难度评级或者对尚未达到高手的数独爱好者提供培训~感觉还是挺有用的呢~O(∩_∩)O~不枉我耽误了将近一天在上面了吧~
附:完整代码:
#include <stdio.h>
#include <string.h>
#include <queue>
using namespace std;
class CSudoku
{
public:
int num[9][9];
int b_num[9][9];
int n_num[9][9];
int s_n;
CSudoku(int a[9][9])
{
for (int i = 0; i < 9; i++)
{
for (int j = 0; j < 9; j++)
{
num[i][j] = a[i][j];
if (a[i][j] == 0)
{
n_num[i][j] = 9;
b_num[i][j] = (1 << 9) - 1;
}
else
{
n_num[i][j] = 0;
b_num[i][j] = 1 << (num[i][j] - 1);
s_n++;
}
}
}
Checkallpoint();
}
void Checkpoint(int x, int y)
{
if (num[x][y] == 0)
printf("error\n");
queue <pair<int,int>> q;
while(!q.empty())
q.pop();
q.push(make_pair(x, y));
while(!q.empty())
{
int pa = q.front().first;
int pb = q.front().second;
q.pop();
for (int i = 0; i < 9; i++)
{
if (num[pa][i] != 0)
continue;
if ((b_num[pa][i] & b_num[pa][pb]) == 0)
continue;
b_num[pa][i] = b_num[pa][i] ^ b_num[pa][pb];
n_num[pa][i]--;
if (n_num[pa][i] == 1)
{
n_num[pa][i] = 0;
q.push(make_pair(pa,i));
int tr = b_num[pa][i];
while(tr)
{
num[pa][i]++;
tr = tr >> 1;
}
}
}
for (int i = 0; i < 9; i++)
{
if (num[i][pb] != 0)
continue;
if ((b_num[i][pb] & b_num[pa][pb]) == 0)
continue;
b_num[i][pb] = b_num[i][pb] ^ b_num[pa][pb];
n_num[i][pb]--;
if (n_num[i][pb] == 1)
{
n_num[i][pb] = 0;
q.push(make_pair(i,pb));
int tr = b_num[i][pb];
while(tr)
{
num[i][pb]++;
tr = tr >> 1;
}
}
}
for (int i = pa / 3 * 3; i < pa / 3 * 3 + 3; i++)
{
for (int j = pb / 3 * 3; j < pb / 3 * 3 + 3; j++)
{
if (num[i][j] != 0)
continue;
if ((b_num[i][j] & b_num[pa][pb]) == 0)
continue;
b_num[i][j] = b_num[i][j] ^ b_num[pa][pb];
n_num[i][j]--;
if (n_num[i][j] == 1)
{
n_num[i][j] = 0;
q.push(make_pair(i,j));
int tr = b_num[i][j];
while(tr)
{
num[i][j]++;
tr = tr >> 1;
}
}
}
}
}
}
void Checkallpoint()
{
for (int i=0;i<9;i++)
{
for (int j=0;j<9;j++)
{
if (num[i][j] == 0)
continue;
Checkpoint(i,j);
}
}
}
void Checklinei(int l)
{
for (int i = 0; i < 9; i++)
{
for (int j = 0; j < 9; j++)
if (num[l][j] == i + 1)
goto next_num;
int check_num = 1 << i;
int n = 0, p;
for (int j = 0; j < 9; j++)
{
if (check_num & b_num[l][j])
{
n++;
p = j;
}
if (n > 1)
goto next_num;
}
num[l][p] = i + 1;
b_num[l][p] = check_num;
n_num[l][p] = 0;
Checkpoint(l,p);
next_num:;
}
}
void Checklinej(int l)
{
for (int i = 0; i < 9; i++)
{
for (int j = 0; j < 9; j++)
if (num[j][l] == i + 1)
goto next_num;
int check_num = 1 << i;
int n = 0, p;
for (int j = 0; j < 9; j++)
{
if (check_num & b_num[j][l])
{
n++;
p = j;
}
if (n > 1)
goto next_num;
}
num[p][l] = i + 1;
b_num[p][l] = check_num;
n_num[p][l] = 0;
Checkpoint(p,l);
next_num:;
}
}
void Checkblock(int x,int y)
{
for (int k = 0; k < 9; k++)
{
for (int i = x * 3;i < x * 3 + 3; i++)
for (int j = y * 3;j < y * 3 + 3; j++)
if (num[i][j] == k + 1)
goto next_num;
int check_num = 1 << k;
int n = 0, px, py;
for (int i = x * 3;i < x * 3 + 3; i++)
{
for (int j = y * 3;j < y * 3 + 3; j++)
{
if (check_num & b_num[i][j])
{
n++;
px = i;
py = j;
}
if (n > 1)
goto next_num;
}
}
num[px][py] = k + 1;
b_num[px][py] = check_num;
n_num[px][py] = 0;
Checkpoint(px, py);
next_num:;
}
}
void Checkallnum()
{
for (int i = 0; i < 9; i++)
Checklinei(i);
for (int i = 0; i < 9; i++)
Checklinej(i);
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
Checkblock(i, j);
}
void Checkblockline(int x, int y)
{
for (int k = 0; k < 9; k++)
{
for (int i = 3 * x; i < 3 * x + 3; i++)
for (int j = 3 * y; j < 3 * y + 3; j++)
if (num[i][j] == k + 1)
goto next_num;
int check_num = 1 << k;
int xs[10],l_x = 0;
int ys[10],l_y = 0;
for (int i = 3 * x; i < 3 * x + 3; i++)
{
for (int j = 3 * y; j < 3 * y + 3; j++)
{
if (b_num[i][j] & check_num)
{
xs[l_x] = i;l_x++;
ys[l_y] = j;l_y++;
}
}
}
for (int i = 1; i < l_x; i++)
{
if (xs[i] != xs[0])
goto test_y;
}
for (int i = 0; i < 9; i++)
{
if (num[xs[0]][i] != 0)
continue;
if (i >= 3 * y && i < 3 * y + 3)
continue;
if ((b_num[xs[0]][i] & check_num) == 0)
continue;
b_num[xs[0]][i] = b_num[xs[0]][i] ^ check_num;
n_num[xs[0]][i]--;
if (n_num[xs[0]][i] == 1)
{
n_num[xs[0]][i] = 0;
int tr = b_num[xs[0]][i];
while(tr)
{
num[xs[0]][i]++;
tr = tr >> 1;
}
Checkpoint(xs[0],i);
}
}
test_y:;
for (int i = 1; i < l_y; i++)
{
if (ys[i] != ys[0])
goto next_num;
}
for (int i = 0; i < 9; i++)
{
if (num[i][ys[0]] != 0)
continue;
if (i >= 3 * x && i < 3 * x + 3)
continue;
if ((b_num[i][ys[0]] & check_num) == 0)
continue;
b_num[i][ys[0]] = b_num[i][ys[0]] ^ check_num;
n_num[i][ys[0]]--;
if (n_num[i][ys[0]] == 1)
{
n_num[i][ys[0]] = 0;
int tr = b_num[i][ys[0]];
while(tr)
{
num[i][ys[0]]++;
tr = tr >> 1;
}
Checkpoint(i, ys[0]);
}
}
next_num:;
}
}
void Checkblocklineall()
{
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
Checkblockline(i,j);
}
//函数功能 检查第l行中的数对情况并标记
void Checkpairlinei(int l)
{
int b_[10]={0};//b_[i]标记i可以放的位置的明细
int n_[10]={0};//n_[i]标记i可以放的位置的数目
for (int k = 1; k <= 9; k++)
{
for (int i = 0; i < 9; i++)
{
if (b_num[l][i] & (1 << (k - 1)))
{
n_[k]++;
b_[k] += 1 << i;
}
}
}
//以上初始化b_,n_
int m[10][10];
int lm[10]={0};
//lm[i]标记m[i]的长度m[i][j]标记有i个可放位置的数的明细
for (int i = 1; i <= 9; i++)
{
m[n_[i]][lm[n_[i]]] = i;
lm[n_[i]]++;
}
//以上初始化m,lm
for (int i = 0; i < lm[2]; i++)
{
for (int j = i + 1; j < lm[2]; j++)
{
if (b_[m[2][i]] != b_[m[2][j]])
continue;
int rb = (1 << (m[2][i] - 1)) + (1 << (m[2][j] - 1));
for (int f = 0; f < 9; f++)
{
if (b_[m[2][i]] & (1 << f))
{
n_num[l][f] = 2;
b_num[l][f] = rb;
}
}
}
}
//以上对2元锁进行标记/*
}
//函数功能 检查第l列中的数对情况并标记
void Checkpairlinej(int l)
{
int b_[10]={0};//b_[i]标记i可以放的位置的明细
int n_[10]={0};//n_[i]标记i可以放的位置的数目
for (int k = 1; k <= 9; k++)
{
for (int i = 0; i < 9; i++)
{
if (b_num[i][l] & (1 << (k - 1)))
{
n_[k]++;
b_[k] += 1 << i;
}
}
}
//以上初始化b_,n_
int m[10][10];
int lm[10]={0};
//lm[i]标记m[i]的长度m[i][j]标记有i个可放位置的数的明细
for (int i = 1; i <= 9; i++)
{
m[n_[i]][lm[n_[i]]] = i;
lm[n_[i]]++;
}
//以上初始化m,lm
for (int i = 0; i < lm[2]; i++)
{
for (int j = i + 1; j < lm[2]; j++)
{
if (b_[m[2][i]] != b_[m[2][j]])
continue;
int rb = (1 << (m[2][i] - 1)) + (1 << (m[2][j] - 1));
for (int f = 0; f < 9; f++)
{
if (b_[m[2][i]] & (1 << f))
{
n_num[f][l] = 2;
b_num[f][l] = rb;
}
}
}
}
//以上对2元锁进行标记/*
}
//函数功能 检查第x,y块中的数对情况并标记
void Checkpairblock(int x, int y)
{
int b_[10]={0};//b_[i]标记i可以放的位置的明细
int n_[10]={0};//n_[i]标记i可以放的位置的数目
for (int k = 1; k <= 9; k++)
{
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 3; j++)
{
if (b_num[i + 3 * x][j+ 3 * y] & (1 << (k - 1)))
{
n_[k]++;
b_[k] += 1 << (i * 3 + j);
}
}
}
}
//以上初始化b_,n_
int m[10][10];
int lm[10]={0};
//lm[i]标记m[i]的长度m[i][j]标记有i个可放位置的数的明细
for (int i = 1; i <= 9; i++)
{
m[n_[i]][lm[n_[i]]] = i;
lm[n_[i]]++;
}
//以上初始化m,lm
for (int i = 0; i < lm[2]; i++)
{
for (int j = i + 1; j < lm[2]; j++)
{
if (b_[m[2][i]] != b_[m[2][j]])
continue;
int rb = (1 << (m[2][i] - 1)) + (1 << (m[2][j] - 1));
for (int f = 0; f < 9; f++)
{
if (b_[m[2][i]] & (1 << f))
{
n_num[f / 3 + 3 * x][f % 3 + 3 * y] = 2;
b_num[f / 3 + 3 * x][f % 3 + 3 * y] = rb;
}
}
}
}
//以上对2元锁进行标记/*
}
void Checkpairall()
{
for (int i = 0; i < 9; i++)
Checkpairlinei(i);
for (int i = 0; i < 9; i++)
Checkpairlinej(i);
for (int i = 0; i < 3; i++)
for (int j = 0; j < 3; j++)
Checkpairblock(i, j);
}
bool CountS()
{
s_n = 0;
for (int i = 0; i < 9; i++)
for (int j = 0; j < 9; j++)
if (num[i][j] != 0)
s_n++;
return (s_n == 81);
}
void Checksudoku()
{
for (int i = 0; i < 9; i++)
{
int ch[9];
for (int j = 0; j < 9; j++)
ch[j] = 2;
for (int j = 0; j < 9; j++)
if (num[i][j] - 1 >= 0)
ch[num[i][j] - 1]--;
for (int j = 0; j < 9; j++)
if (ch[j] != 1)
printf("!");
for (int j = 0; j < 9; j++)
if (num[j][i] - 1 >= 0)
ch[num[j][i] - 1]--;
for (int j = 0; j < 9; j++)
if (ch[j] != 0)
printf("!");
}
for (int x = 0; x < 3; x++)
{
for (int y = 0; y < 3; y++)
{
int ch[9];
for (int j = 0; j < 9; j++)
ch[j] = 1;
for (int i = 3 * x; i < 3 * x + 3; i++)
for (int j = 3 * y; j < 3 * y + 3; j++)
if (num[i][j] - 1 >= 0)
ch[num[i][j] - 1]--;
for (int j = 0; j < 9; j++)
if (ch[j] != 0)
printf("!");
}
}
}
void Checkprogram()
{
printf("\n");
for (int i = 0; i < 9; i++)
{
for (int j = 0; j < 9; j++)
printf("%d ", num[i][j]);
printf("\n");
}
printf("\n");/*
for (int i = 0; i < 9; i++)
{
for (int j = 0; j < 9; j++)
printf("%d ", n_num[i][j]);
printf("\n");
}
printf("\n");
for (int i = 0; i < 9; i++)
{
for (int j = 0; j < 9; j++)
printf("%d ", b_num[i][j]);
printf("\n");
}
printf("\n");*/
}
};
int main ()
{
FILE *fp = fopen("p96.txt", "r");
CSudoku * Sudoku;
int ans=0;
int r_ans=0;
for (int T=0;T<50;T++)
{
char c[10][100]={0};
int a[9][9];
for (int i=0;i<10;i++)
fgets(c[i], 100, fp);
for (int i=1;i<10;i++)
for (int j=0;j<9;j++)
a[i-1][j]=c[i][j]-'0';
Sudoku = new CSudoku(a);
for (int ii=0;;ii++)
{
Sudoku->Checkallnum();
Sudoku->Checkblocklineall();
Sudoku->Checkpairall();
if (Sudoku->CountS())
{
r_ans+=Sudoku->num[0][0]*100+Sudoku->num[0][1]*10+Sudoku->num[0][2];
break;
}
if (ii>100)
{
ans++;
Sudoku->Checkprogram();
break;
}
}
Sudoku->Checksudoku();
int rrr=1;
delete Sudoku;
}
while(1);
return 0;
}