P1074靶型数独题解
以下内容是从作者以前的博客搬运过来的旧文章
题目来源 P1074 [NOIP2009 提高组] 靶形数独
参考题解: here and here
思路分析
乍一看好像很复杂,我们不妨先不考虑分数,只考虑如何解出给定的数独问题。那么我们只需要枚举81个格子。对于每个格子上的数,它必须满足是它所在的行、列以及小九宫格中唯一的一个数,然后试一试0-9中哪个数字符合就把它填上开始搜索下一个,所以用DFS就可以解决。
那么如何判断所处的行、列和小九宫格呢?我们可以参考八皇后问题中的做法,用一个三维布尔数组\(vis[]\)对行、列或者九宫格进行标记,例如:
vis[0][1][2] = 1; //说明第一行"2"这个数字已经被填了
vis[1][2][3] = 0; //第二列"3"还没有填
vis[2][4][3] = 1; //第四个九格"3"已经填了
接下来需要考虑的是判断当前的格子处在哪一个九宫格中、计算当前格子的占分是多少,对于以上内容,我们可以直接打一个表,为了省劲也可以写一个函数判断。
那么解决思路就很清晰了:从第一个格子开始搜索,如果这个格子没有被填,就尝试填一个可以满足条件的数;这个格子结束后搜索下一个格子,如果81个格子都搜索结束就计算一下当前所填的数的分数,并且记录最大值。最后如果存在一个这样的最大值就是我们的答案,否则无解,输出\(-1\)。
预处理
按照思路,首先我们要写出判断格子处于哪个九宫格中的函数以及计算当前格子占分多少的函数,实现起来非常容易:
int work1(int x, int y) { //判断(x, y)是在哪个九宫格内
if(x >= 1 && x <= 3) {
if(y >= 1 && y <= 3) return 1;
else if(y >= 4 && y <= 6) return 2;
else return 3;
}
else if(x >= 4 && x <= 6) {
if(y >= 1 && y <= 3) return 4;
else if(y >= 4 && y <= 6) return 5;
else return 6;
}
else {
if(y >= 1 && y <= 3) return 7;
else if(y >= 4 && y <= 6) return 8;
else return 9;
}
}
int work2(int x, int y) { //判断(x, y)占分是多少
if(x == 1 || y == 1 || x == 9 || y == 9) return 6;
else if(x == 2 || y == 2 || x == 8 || y == 8) return 7;
else if(x == 3 || y == 3 || x == 7 || y == 7) return 8;
else if(x == 4 || y == 4 || x == 6 || y == 6) return 9;
else return 10;
}
另外我们还需要能够计算最后分数总和的函数,在此之前我们需要一个二维数组\(score[]\)存放填入的(还有本来就有的)数字:
int calc(void) {
int sum = 0;
for(int i = 1; i <= 9; i++)
for(int j = 1; j <= 9; j++)
sum += sroce[i][j] * work2(i, j);
return sum;
}
另外我们还意识到,如果直接搜搜搜,非常有可能超时,大家在解数独时为了降低复杂程度都会优先选择从空缺少的地方开始,所以为了减少搜索的时间,我们也可以优先从空缺少的行(放到题目中就是\(0\))多的行开始,所以我们可以用一个结构体数组存放当前的行号和\(0\)的个数,然后对这个结构体进行一次排序:
struct row{
int line, cnt;
}r[N];
bool cmp(row r1, row r2) {
return r1.cnt < r2.cnt;
}
然后就可以在程序中进行排序了。
完整代码
#include <iostream>
#include <algorithm>
const int N = 10;
using namespace std;
int mp[N][N], b[82], score[N][N];
bool vis[3][N][N];
int maxn = -1;
struct row{
int line, cnt;
}r[N];
bool cmp(row r1, row r2) {
return r1.cnt < r2.cnt;
}
int work1(int x, int y) { //判断(x, y)是在哪个九宫格内
if(x >= 1 && x <= 3) {
if(y >= 1 && y <= 3) return 1;
else if(y >= 4 && y <= 6) return 2;
else return 3;
}
else if(x >= 4 && x <= 6) {
if(y >= 1 && y <= 3) return 4;
else if(y >= 4 && y <= 6) return 5;
else return 6;
}
else {
if(y >= 1 && y <= 3) return 7;
else if(y >= 4 && y <= 6) return 8;
else return 9;
}
}
int work2(int x, int y) { //判断(x, y)占分是多少
if(x == 1 || y == 1 || x == 9 || y == 9) return 6;
else if(x == 2 || y == 2 || x == 8 || y == 8) return 7;
else if(x == 3 || y == 3 || x == 7 || y == 7) return 8;
else if(x == 4 || y == 4 || x == 6 || y == 6) return 9;
else return 10;
}
int calc(void) {
int sum = 0;
for(int i = 1; i <= 9; i++)
for(int j = 1; j <= 9; j++)
sum += score[i][j] * work2(i, j);
return sum;
}
void dfs(int n) {
if(n == 82) {
maxn = max(maxn, calc());
return;
}
int x = b[n] / 9 + 1;
int y = b[n] % 9;
if(y == 0) { //如果y == 0,说明是在第九列
x = b[n] / 9;
y = 9;
}
if(!mp[x][y]) { //只有当前位置是0才搜索
for(int i = 1; i <= 9; i++) {
int g = work1(x, y);
if(!vis[0][x][i] && !vis[1][y][i] && !vis[2][g][i]) {
vis[0][x][i] = vis[1][y][i] = vis[2][g][i] = 1;
score[x][y] = i;
dfs(n + 1);
vis[0][x][i] = vis[1][y][i] = vis[2][g][i] = 0;
}
}
}
else {
dfs(n + 1);
}
}
int main(void) {
for(int i = 1; i <= 9; i++) {
int cnt = 0;
for(int j = 1; j <= 9; j++) {
cin >> mp[i][j];
if(mp[i][j] == 0) {
cnt++;
} else {
int v = mp[i][j];
int g = work1(i, j);
vis[0][i][v] = vis[1][j][v] = vis[2][g][v] = 1;
score[i][j] = v;
}
}
r[i].line = i, r[i].cnt = cnt;
}
sort(r + 1, r + 1 + 9, cmp);
int num = 0;
for(int i = 1; i <= 9; i++) {
for(int j = 1; j <= 9; j++) {
int x = r[i].line;
int y = j;
b[++num] = (x - 1) * 9 + y; //(x - 1) * 9 + y实际上存储 了当前是第几行第几列,设 a = (x - 1) * 9 + y,那么行号x = a / 9 + 1,列 号y = a % 9
}
}
dfs(1);
cout << maxn << endl;
return 0;
}

浙公网安备 33010602011771号