题目:https://www.luogu.org/problemnew/show/P1074
思路:
1、可以看作八皇后的升级版,八皇后递归深度是8层,每一层供选择的算符有8个。
本题递归深度为cns层,其中,cns为要填的数字总数,每一层供选择的算符为9个。理解了这一点,写DFS有两种写法。
第一种是按要填入数字的格子DFS,后面的AC代码是这一种写法。
另一种写法是逐行逐列地DFS,结构如下:
void dfs(int row,int col){
if(row==10){ cal(),return; }
if(col==10)dfs(row+1,1);//列到头后往下一行
...
dfs(row,col+1);//同一行逐列
...
}
2、剪枝:引入三个数组,a[row][k],b[col][k],c[palace][k],分别表示数字k在行、列、宫有没有被用过。
3、优化:
1)从数字0最多的一行开始DFS,这样搜索树的枝可以少许多。因为某一行填入数字x,那么这个数字所在的列、宫就不能再填x,一些情况可以被快速地剪枝掉。根据排列组合知识,如果需要填入数字的个数是4与6,前者比后者的情况要少6!-4!=720-24种。
为此,引入结构体:
struct Node{
int data,row;/*data表示0的个数,row表示行号*/
};
Node Zero[10];/*记录每行需要填的0的个数*/
int cmp(const Node &a,const Node &b){
return a.data
然后用sort对结构体数组Zero排序。
2)预处理,DFS前将待填格子的坐标、宫号、赋分值存到数组srch[][]里,避免频繁调用函数,没有这一步会被卡掉一个点。
3)其它优化:一是对宫号、赋分值打表;二是用getchar读入;三是用inline。但下面的AC代码并没有用这些技巧。
AC代码:
/*预处理时存入每个非0点的坐标、宫号、对应赋分值,
否则会被卡掉一个点
——调取函数是非常耗时的*/
#include
#include
#include
#include
using namespace std;
int sudoku[10][10];/*数独sudoku*/
int maxx=-1;/*无解输出-1*/
bool a[10][10],b[10][10],c[10][10];/*标记行、列、宫是否占领*/
int cns=0;/*待填入的数字个数*/
int srch[100][4];/*行号、列号、宫号、赋分值*/
struct Node{
int data,row;/*data表示0的个数,row表示行号*/
};
Node Zero[10];/*记录每行需要填的0的个数*/
int cmp(const Node &a,const Node &b){
return a.dataabs(j-5)?10-abs(i-5):10-abs(j-5);
}
int plc(int i,int j){/*计算点(i,j)所处宫的序号*/
int x=(i-1)/3,y=(j-1)/3+1;
return x*3+y;
}
void dfs(int N,int sum){
if(N==cns+1){
if(maxx>sudoku[i][j];
int X=sudoku[i][j];
if(!X)Zero[i].data++;
else{
int palace=plc(i,j);
a[i][X]=1;b[j][X]=1;c[palace][X]=1;
sum+=X*scr(i,j);
}
}
}
sort(Zero+1,Zero+10,cmp);/*将行号按0的个数从少到多排序*/
for(int No=1;No<=9;No++){
int row=Zero[No].row;
for(int col=1;col<=9;col++)
if(!sudoku[row][col]){
srch[++cns][0]=row;/*cns统计要填写的数字个数*/
srch[cns][1]=col;
srch[cns][2]=plc(row,col);
srch[cns][3]=scr(row,col);
}
}
dfs(1,sum);
cout<