N 皇后搜索:逐行放置,用列与两条对角线的位掩码 O(1) 判冲突,按列号升序展开保证字典序,输出前 3 个解与总解数。
OJ: roj
题目 ID: 2020
难度:普及-
标签:搜索回溯位运算python
创建: 2026-10-01 03:20
更新: 2026-10-06 09:41
题目描述
在
输入:一个整数
输出:前三行各为一个解,数字间用空格隔开;第四行为解的总数。
样例输入 6 的样例输出:
text
2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4思路
逐行 DFS 放置棋子,用三个整数位掩码分别记录已占用的列、主对角线(free & -free 取最小合法列,天然按字典序搜索。到达第
参考代码
Python 版:
python
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-10-01 03:20
# update_at: 2026-10-01 03:20
import sys
SHOW = 3 # 题面只要求前 3 个解,而按列号升序展开时它们正好是字典序最小的 3 个
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
n = next(data)
full = (1 << n) - 1 # 低 n 位全 1:第 c 位为 1 表示列 c 还没被占
head: list[list[int]] = [] # 前 SHOW 个解,每个解是各行的列号
pos = [0] * n # pos[row] 是第 row 行的列号;放棋子即写入,回溯即被覆盖
total = 0
def dfs(row: int, col: int, diag1: int, diag2: int) -> None:
"""在第 row 行及其之后摆完剩下的棋子:三个掩码是已被占用的列 / 两条斜线。"""
nonlocal total
if row == n: # n 行都放好了,得到一个完整解
total += 1
if len(head) < SHOW:
head.append(pos.copy()) # 不复制的话,回溯会把这份棋盘改掉
return
free = full & ~(col | diag1 | diag2) # 本行所有合法列
while free:
bit = free & -free # 取列号最小的合法列,同一层天然按字典序展开
free ^= bit
pos[row] = bit.bit_length() # bit = 1 << (列号-1),bit_length() 还原成 1 起算的列号
# r+c 与 r-c 分别由 diag1、diag2 记录,走到下一行时两条斜线朝相反方向平移一位
dfs(row + 1, col | bit, (diag1 | bit) << 1, (diag2 | bit) >> 1)
dfs(0, 0, 0, 0)
print('\n'.join(' '.join(map(str, solution)) for solution in head))
print(total)
if __name__ == "__main__":
solve()C++ 版(同一算法):
cpp
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-10-06 09:41
* update_at: 2026-10-06 09:41
*/
#include <iostream>
using namespace std;
typedef long long ll;
const int MAXN = 15;
const int SHOW = 3; // 题面只要求输出前 3 个解
int n;
int pos[MAXN]; // pos[row] 表示第 row 行棋子所在的列号(从 1 开始)
int total; // 解的总数
int head[MAXN][MAXN]; // 保存前 SHOW 个解
int head_cnt; // 已保存的解数
// 在第 row 行放置棋子,col/diag1/diag2 为已占用的列、主对角线、副对角线的位掩码
void dfs(int row, int col, int diag1, int diag2) {
if (row == n) { // n 行都放好了,得到一个完整解
++total;
if (head_cnt < SHOW) {
for (int i = 0; i < n; ++i) {
head[head_cnt][i] = pos[i];
}
++head_cnt;
}
return;
}
int full = (1 << n) - 1; // 低 n 位全 1
int free = full & ~(col | diag1 | diag2); // 本行所有合法列
while (free) {
int bit = free & -free; // 取列号最小的合法列,保证字典序
free ^= bit;
pos[row] = __builtin_ctz(bit) + 1; // bit = 1 << (列号-1),还原列号
// 下一行:列直接合并;两条对角线朝相反方向各平移一位
dfs(row + 1, col | bit, (diag1 | bit) << 1, (diag2 | bit) >> 1);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n;
total = 0;
head_cnt = 0;
dfs(0, 0, 0, 0);
for (int k = 0; k < head_cnt; ++k) {
for (int i = 0; i < n; ++i) {
if (i) cout << ' ';
cout << head[k][i];
}
cout << '\n';
}
cout << total << '\n';
return 0;
}