点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 15;
char g[N][N];
int n;
int cnt;
// 是一个二进制数,表示第i行的哪些位置可以放皇后,1表示可放,0表示不可以放
int row_mask[N];
//熟悉几个位运算的逻辑就可以了,原理和acwing的基本方法一致,也用到了传值的巧妙,所以不用恢复
//按行进行搜索
//col,dg,udg表示哪些列,对角线,副对角线已经有皇后了
//这个主对角线的设置也是相当阴间,我要重新来理一下这个位运算的逻辑
//1.因为位运算么,也就是只能表示一行,所以这个dfs我们就依次搜索行,这和原来的原理是一样的
//那么我们如何基于一行来表达整个棋盘的占用状态呢
//row存储所有行(每个行是二进制数字),在主函数中读取,它的作用是表示这个位置是否可以设置皇后
//col表示列,如果一列放置皇后了,那么将这个位置置成1就可以了,一行col可以表示所有棋盘的列占用状态
//重点就是dg和udg
//在原本的题目中,一个矩阵的对角线的和是相同的,我们开了一个一维数组,每一个i+u和值表示一个对角线,所有i+u的值表示所有对角线
//但这里我们每次只能看到一行,因此假设一个位置放了皇后,那么我们依据行搜索的顺序,就可以推断出下一个搜索行哪个位置放了皇后
//通过左移右移递推即可
//2.这样,我们就找到了可以在一行中反应一个棋盘状态的所有参数
//合并为avi就是当前行可放置的所有位置
//然后,我们从尝试从右放置皇后,(因为取1方便)
//也就是说整体的遍历方式是
//<-----st
//<------
//<------
//3.因为传值的方式,所以我们对col,dg不需要恢复状态
//我们要将dfs回溯的上下文代码看作一体,我发现只要dfs之上有修改的都要看作一体
//一种状态处理完毕后,回来之后标记这个这个位置的皇后被放置,继续搜索下一行
//也可以认为是决策的两种选择
void dfs(int u, int col_mask, int dg_mask, int udg_mask) {
if (u == n) {
cnt++;
return;
}
// ’当前行‘所有可放置的位置 = 棋盘允许的位置 & 列不冲突 & 对角线不冲突
//对于col,dg,udg中,0表示可以放,和row的逻辑相反,因此要取反
int available = row_mask[u] & (~col_mask) & (~dg_mask) & (~udg_mask);
// 枚举所有可以放置的位置
while (available) {
// 取出最低位的1(最右边的可用位置)
//取得最低位操作的原理是,取反后原来低位为0的都被置换为1,1被换为0,+1后1变为0,原来的1恢复1
//pos表示放置皇后的位置
int pos = available & -available;
//这相当于是一次选择吧。先选择在这里放皇后,然后搜索
//之后不管是成功还是失败都会回到这里,继续探索,available-pos标记已经尝试完这个位置放皇后的所有可能性了,接着尝试不放皇后的可能性
// 放置皇后,递归下一行,或运算表示将某一位置成1
//相当于还是一种恢复状态
dfs(u + 1, col_mask | pos, (dg_mask | pos) >> 1, (udg_mask | pos) << 1);
available -= pos;
}
}
int main() {
cin >> n;
// 读取棋盘并生成每行的掩码
for (int i = 0; i < n; i++) {
row_mask[i] = 0; // 初始化
for (int j = 0; j < n; j++) {
cin >> g[i][j];
if (g[i][j] == '*') {
// 将每一行的row_mask对应的位置设成1,表示可以放
row_mask[i] |= (1 << (n - 1 - j));
}
}
}
// 从第0行开始搜索
dfs(0, 0, 0, 0);
cout << cnt;
return 0;
}
N皇后问题的二进制方法,感觉还是有些地方理解的不透彻,先到这种程度吧