题目
思路
- 思考问题:搜索顺序->考虑剪枝
- 搜索顺序:先随意选择一个空格子,枚举该格子可填写的数字,当所有格子都填完的时候,说明可以退出了
- 剪枝:
- 优化搜索顺序:随意选择一个空格子:应该优先搜索分支数量较少的方案,如果分支数量相同,则选择前者
- 可行性剪枝:当前数字不能与行,列,九宫格有重复
- 本题用到了位运算优化:9位的01串:0表示尚未用过,1表示用过;与运算
行:123456789
行01:001101010
列01:...
九01:...
lowbit运算:返回01串的1 - AcWing 801. 二进制中1的个数 - AcWing
代码
#include <iostream>
#define lowbit(x) (x & -x) // lowbit操作
#define get(x, y) (row[x] & col[y] & cell[x / 3][y / 3]) // get(x, y) 找到该位置可以填哪些数的状态
using namespace std;
const int N = 9, M = 1 << N;
int one[M], map[M]; // one[state]为该state中有几个1, map[state]为state对应的十进制值
int col[N], row[N], cell[3][3];
char str[100];
void init() { // 初始化(将所有位置都初始化可以填数的状态)
for (int i = 0; i < N; ++ i) row[i] = col[i] = (1 << N) - 1;
// 将行和列都用二进制来优化(刚开始的位置都为1)
for (int i = 0; i < 3; ++ i)
for (int j = 0; j < 3; ++ j)
cell[i][j] = (1 << N) - 1; // 每个3 * 3的小方格也用二进制来优化(刚开始也都为1)
}
void draw(int x, int y, int t, bool is_set) { // 在(x, y)的位置上(is_set)<是/否>填t的操作,
// t 是待填入或待删去的二进制字符串state的下标(0..8)
//t + ‘1’ 是因为由于你传入的是二进制字符串state的下标,所以 draw 如果是填入操作,那么即需要 t + ‘1’
if (is_set) str[x * N + y] = '1' + t; // 如果填数的话, 将该数转换为字符形式填入字符串中对应的位置
else str[x * N + y] = '.'; // 否则说明字符串该位置上填的是'.';
int v = 1 << t; // 找到该数对应二进制之后的位置的数
if (!is_set) v = -v; // 如果该位置不填数,则将该数取负
row[x] -= v; //在这个原数对应的行减去该数的二进制数
col[y] -= v; // 在这个原数对应的列减去该数的二进制数
cell[x / 3][y / 3] -= v; // 在这个原数对应的小方格减去该数的二进制数
}
bool dfs(int cnt) {
if (!cnt) return true; // 知道没有位置能填数就结束搜索
int minv = 10; // 记录当前最少枚举方案
int x, y; // x, y记录枚举方案最少的位置的x, y
for (int i = 0; i < N; ++ i)
for (int j = 0; j < N; ++ j)
if (str[i * N + j] == '.') { // 该位置对应的字符串位置上为'.', 才说明能填数
int state = get(i, j); // 找到该位置上能填的数的状态
if(one[state] < minv) { // 只有当当前位置的方案少于当前最少方案才有搜索的必要
x = i, y = j;
minv = one[state];
}
}
int state = get(x, y); // 找到最少枚举方案对应的位置的能填的数的状态
for (int i = state; i; i -= lowbit(i)) { // 枚举该位置上能填的数,用lowbit操作
int t = map[lowbit(i)]; // 找到该位置上能填的数
draw(x, y, t, true); // 填数
if (dfs(cnt - 1)) return true; // 继续搜索
draw(x, y, t, false); // 恢复
}
return false;
}
int main() {
for (int i = 0; i < N; ++ i) map[1 << i] = i; // 预处理map[]
for (int i = 0; i < 1 << N; ++ i)
for (int j = 0; j < N; ++ j)
one[i] += (i >> j & 1); // 预处理one[]
while (cin >> str, str[0] != 'e') { // 多组输入
init(); // 初始化
int cnt = 0; // 记录有几个空格需要填数
for (int i = 0, k = 0; i < N; ++ i)
for(int j = 0; j < N; ++ j, ++ k) {
if (str[k] != '.') { // 如果该位置已经有数了
int t = str[k] - '1'; // 找到该位置上的数
draw(i, j, t, true); // 在该位置上填上该数
}
else cnt ++ ; // 否则说明该位置需要填数
}
dfs(cnt); // 开始搜索
puts(str); // 输出答案
}
return 0; // 结束ing~
}