P1074 靶形数独
输出格式
输出共 11 行。输出可以得到的靶形数独的最高分数。如果这个数独无解,则输出整数-1−1。
输入输出样例
输入 #1复制
7 0 0 9 0 0 0 0 1
1 0 0 0 0 5 9 0 0
0 0 0 2 0 0 0 8 0
0 0 5 0 2 0 0 0 3
0 0 0 0 0 0 6 4 8
4 1 3 0 0 0 0 0 0
0 0 7 0 0 2 0 9 0
2 0 1 0 6 0 8 0 4
0 8 0 5 0 4 0 1 2
输出 #1复制
2829
输入 #2复制
0 0 0 7 0 2 4 5 3
9 0 0 0 0 8 0 0 0
7 4 0 0 0 5 0 1 0
1 9 5 0 8 0 0 0 0
0 7 0 0 0 0 0 2 5
0 3 0 5 7 9 1 0 8
0 0 0 6 0 1 0 0 0
0 6 0 9 0 0 0 0 1
0 0 0 0 0 0 0 0 6
输出 #2复制
2852
总结目录
1 本题搜索思路与加速方法——有优先顺序的dfs
2 具体搜索流程
3 本题搜索细节
1 有优先顺序的dfs
本题其实有点类似于八皇后问题的变种。只不过增加了一个九宫格的blockmark[][]的记录,rowmark[][]和colmark[][]应该都是已知的。那么深搜的时候从第一个点进行搜索,搜到最后一个点得到sum,这个算是暴力搜索的方法了,此时的dfs(int row,int col),返回条件是row==10,也就是搜到10的时候说明搜完了。
但是这样暴力搜索的话存在一个问题,就是一开始可能会有很多0,这样会展开非常多的分支,由于每次都要搜索到底部,因此是很费时的。所以我们会考虑,如何去进行更加有效率的搜索。实际上在日常中,我们做数独往往都是从数字多的地方进行填,这样可以提高我们的效率,减少无效的枚举。 虽然我暂时还无法从理论上证明为什么这么做可以加速,但是这就是本题的加速方法。
也就是说我们在深搜的时候,根据已知条件(本题中是每一行0的多少,0少的值得深搜),给出一个深搜的权重,我们会去优先搜索这些点,以此来进行加速。 也就是我们给出了一个深搜的加速路径,这样可以提高搜索的效率。
2 具体搜索的流程
如果给定一个搜索路径,那么我们要先把路径给确定下来。本题中我使用了Point结构体,然后根据每一行的0的多少进行填写搜索的路径。填写完之后,进行具体的搜索。这样做的原因是,如果使用dfs(int row,int col)的话,具体dfs的时候,在程序中可能不知道下一个要dfs的地方是那里,这里先确定了路径,然后按照点来进行深搜会更好。
另外,对于权重的选取,在输入数据的时候就统计了每一行的0的个数,然后使用sort函数进行统计,自己写了比较器。然后在main中将路径写进points[]数组中。
深搜的过程到达尽头更新flag和maxres就可以了,使用的回溯算法也没有什么好说的。
3 本题搜索细节
本题一开始我只拿了85分,因我我在深搜中判断并mark已经填的数。但是这些数应该是在读入数据的时候就已经在rowmark[][],colmark[][],blockmark[][]里面记录好了。如果在dfs搜索里面才mark,那么有可能会出现一开始就是0,然后对0枚举了1,但是同一行后面已经有1这样的情况。这样的话就会出错,出现同一行出现2个1的情况。
因此必须在预处理阶段就进行mark,我在这里可能也是受到了虫食算那题的影响。
代码
#include<iostream>
#include<algorithm>
using namespace std;
#define maxsize 15
struct node{
int rowindex;
int zerocnt;
};
int mat[maxsize][maxsize];//存储初始矩阵的值
int rowmark[maxsize][maxsize];//rowrark[row][i]==1表示第row行,数字i已经被使用过
int colmark[maxsize][maxsize];//colmark[col][i]==1表示第col行,数字i已经被使用过
int blockmark[maxsize][maxsize];//nineblock[ind][i]==1表示第ind个九宫,数字i已经被使用过
int maxres = 0;//结果
bool flag = 0;//是否存在满足的答案
node dfsrow[maxsize];//记录了行的搜索顺序,dfsrow[1]表示第一个搜索的行的0是最少的
struct Point {
int row;
int col;
};
Point points[100];
bool cmp(node a, node b) {
return a.zerocnt < b.zerocnt;
}
int loc2blkind(int row, int col) {
//输入[row,col],返回对应的block下标
if (row <= 3) {
if (col <= 3)return 1;
if (col > 3 && col <= 6) return 2;
if (col > 6 && col <= 9) return 3;
}
if (row > 3 && row <= 6) {
if (col <= 3)return 4;
if (col > 3 && col <= 6)return 5;
if (col > 6 && col <= 9)return 6;
}
if (row > 6 && row <= 9) {
if (col <= 3)return 7;
if (col > 3 && col <= 6)return 8;
if (col > 6 && col <= 9)return 9;
}
}
//本题剪枝要点,从0少的地方进行深搜比随机乱搜更快
void getdata() {
for (int row = 1; row <= 9; row++) {
dfsrow[row].rowindex = row;
dfsrow[row].zerocnt = 0;
for (int col = 1; col <= 9; col++) {
cin >> mat[row][col];
if (mat[row][col] != 0) {
int num = mat[row][col];
int blkind = loc2blkind(row, col);
rowmark[row][num] = 1;
colmark[col][num] = 1;
blockmark[blkind][num] = 1;//如果当前的统计点不为0,则打上标记
//这里一定要注意,此处的标记一定是在输入的过程中就打上标记,也就是
//一开始就打上标记,否则在搜索过程中,如果一开始就是0,那么可能会出现
//同一行存在2个1,或者多个1的情况,导致出错!
}
if (mat[row][col] == 0) {
dfsrow[row].zerocnt = dfsrow[row].zerocnt + 1;
}
}
}
sort(dfsrow + 1, dfsrow + 10, cmp);
}
int calsum(int row, int col) {
//参考大佬的代码,发现从外面往里面剥代码会很简单
if (row == 1 || row == 9 || col == 1 || col == 9)return mat[row][col] * 6;
if (row == 2 || row == 8 || col == 2 || col == 8)return mat[row][col] * 7;
if (row == 3 || row == 7 || col == 3 || col == 7)return mat[row][col] * 8;
if (row == 4 || row == 6 || col == 4 || col == 6)return mat[row][col] * 9;
else return mat[row][col]*10;
}
void dfs(int pcnt,int cursum) {
//pcnt表示正在统计第pcnt个点
if (pcnt == 82) {
maxres = max(maxres, cursum);
flag = 1;
return;
}
Point curpoint = points[pcnt];//当前要统计的点
int row = curpoint.row;
int col = curpoint.col;
int blkind = loc2blkind(row, col);
if (mat[row][col] != 0) {//如果当前的统计点不为0,则打上标记,求和之后继续深搜
int addval = calsum(row, col);
dfs(pcnt + 1, cursum + addval);
}
else if (mat[row][col] == 0) {//如果当前的统计点为0,那么就需要枚举
for (int i = 1; i <= 9; i++) {//枚举数字
if (rowmark[row][i] != 1 && colmark[col][i] != 1 && blockmark[blkind][i] != 1) {
rowmark[row][i] = 1;
colmark[col][i] = 1;
blockmark[blkind][i] = 1;
mat[row][col] = i;//标记
int addval = calsum(row, col);
dfs(pcnt + 1, cursum + addval);//这里尽量用加法
rowmark[row][i] = 0;
colmark[col][i] = 0;
blockmark[blkind][i] = 0;
mat[row][col] = 0;//擦除标记,回溯
}
}
}
}
int main() {
getdata();
//计算点的顺序
int cnt = 1;
for (int row = 1; row <= 9; row++) {
for (int col = 1; col <= 9; col++) {
points[cnt].row = dfsrow[row].rowindex;
points[cnt].col = col;
cnt++;
}
}
dfs(1,0);
if (flag == 0) {
cout << "-1" << endl;
}
else if (flag != 0) {
cout << maxres << endl;
}
return 0;
}