题解:洛谷 P1219 八皇后
【题目来源】
洛谷:P1219 [USACO1.5] 八皇后 Checker Challenge - 洛谷
【题目描述】
一个如下的 \(6×6\) 的跳棋棋盘,有六个棋子被放置在棋盘上,使得每行、每列有且只有一个,每条对角线(包括两条主对角线的所有平行线)上至多有一个棋子。

上面的布局可以用序列 \(2\ 4\ 6\ 1\ 3\ 5\) 来描述,第 \(i\) 个数字表示在第 \(i\) 行的相应位置有一个棋子,如下:
行号 \(1\ 2\ 3\ 4\ 5\ 6\)
列号 \(2\ 4\ 6\ 1\ 3\ 5\)
这只是棋子放置的一个解。请编一个程序找出所有棋子放置的解。并把它们以上面的序列方法输出,解按字典顺序排列。请输出前 \(3\) 个解。最后一行是解的总个数。
【输入】
一行一个正整数 \(n\),表示棋盘是 \(n\times n\) 大小的。
【输出】
前三行为前三个解,每个解的两个数字之间用一个空格隔开。第四行只有一个数字,表示解的总数。
【输入样例】
6
【输出样例】
2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4
【核心思想】
-
问题分析:在 \(n \times n\) 棋盘上放置 \(n\) 个皇后,要求每行、每列、每条对角线(包括两条主对角线的所有平行线)上至多一个皇后。求所有解,输出前3个,最后输出解的总数。这是一个经典回溯(DFS)问题,关键在于用三个标记数组分别记录列、主对角线、副对角线的占用状态。
-
算法选择:
- 深度优先搜索(DFS):逐行放置皇后,每行尝试每一列
- 状态标记数组:
c[j]:第 \(j\) 列是否被占用p[i+j]:主对角线(从左上到右下)是否被占用,同一主对角线上的点满足 \(i+j\) 为常数q[i-j+n]:副对角线(从右上到左下)是否被占用,同一副对角线上的点满足 \(i-j\) 为常数(加 \(n\) 防止负数下标)
-
关键步骤:
- 初始化:
ans = 0,标记数组全为0 - DFS
dfs(i)(当前处理第 \(i\) 行):- 若 \(i > n\):
ans++,若ans <= 3输出当前解,返回 - 遍历 \(j\) 从 \(1\) 到 \(n\)(尝试第 \(j\) 列):
- 若
c[j] || p[i+j] || q[i-j+n],跳过(冲突) pos[i] = j,标记三个数组为1dfs(i+1)- 恢复标记为0(回溯)
- 若
- 若 \(i > n\):
- 输出
ans
- 初始化:
-
时间/空间复杂度:
- 时间复杂度:\(O(n!)\),最坏情况遍历所有排列,实际因剪枝大幅减少
- 空间复杂度:\(O(n)\),标记数组和递归栈
-
回溯法的核心思想:
- 逐行决策:每行恰好放一个皇后,将二维问题降为一维序列(\(n\) 个数的排列)
- 冲突检测的 \(O(1)\) 化:利用对角线的数学性质(\(i+j\) 和 \(i-j\) 为常数),将二维坐标映射到一维标记数组,实现 \(O(1)\) 冲突判断
- 剪枝优化:一旦某位置冲突立即跳过,不继续深入,大幅减少搜索空间
- 回溯恢复:递归返回后清除当前行的标记,保证不同分支的独立性
- 字典序自然保证:按行优先、列从小到大的顺序搜索,天然按字典序生成解
- 适用于"组合搜索 + 约束满足"的经典问题,核心在于状态压缩和剪枝策略
【解题思路】

【算法标签】
普及 #DFS-二维
【代码详解】
#include <bits/stdc++.h>
using namespace std;
int n, m1[30]={0}, m2[30]={0}, m3[30]={0}, ans[15]={0}, mark=0;
void setvalue(int x, int y, int k)
{
ans[x] = y; // 记录列的位置
m1[y] = k; // 标记同列
m2[x+y] = k; // 标记同逆对角线
m3[x-y+n] = k; // 标记同顺对角线
}
void dfs(int step)
{
if (step>n) { // 搜索退出条件
mark++; // 记录找到答案的次数
if (mark<=3) { // 不能超过3次
for (int i=1; i<=n; i++) { // 遍历所有列
cout << ans[i] << " "; // 输出每列的值
}
cout << endl; // 每列输出完后需要换行
}
return;
}
for (int j=1; j<=n; j++) { // 遍历n列(step代表行)
if (m1[j] || m2[step+j] || m3[step-j+n]) continue; // 选中某个位置后,就固定同列,同逆,同顺位置,再次搜索时,如果这些位置为1则跳过,(step-j+n)可以保证永远不会小于0
setvalue(step, j, 1); // 根据行和列的位置,进行标记
dfs(step+1); // 继续搜索
setvalue(step, j, 0); // 还原现场
}
}
int main()
{
cin >> n; // 输入n
dfs(1); // 进行dfs深搜
cout << mark << endl;
return 0;
}
#include <bits/stdc++.h>
using namespace std;
const int N = 30;
int n, ans; // n: 棋盘大小, ans: 解的数量
int pos[N], c[N], p[N], q[N]; // 列, 主对角线, 副对角线标记
// 输出前3个解
void print()
{
if (ans <= 3) // 只输出前3个解
{
for (int i = 1; i <= n; i++)
{
cout << pos[i] << " ";
}
cout << endl;
}
}
// 深度优先搜索
void dfs(int i) // 当前处理第i行
{
if (i > n) // 找到解
{
ans++;
print();
return;
}
for (int j = 1; j <= n; j++) // 尝试在第i行的每一列放置皇后
{
if (c[j] || p[i + j] || q[i - j + n]) // 检查冲突
{
continue;
}
pos[i] = j; // 记录第i行放在了第j列
c[j] = p[i + j] = q[i - j + n] = 1; // 宣布占领
dfs(i + 1); // 处理下一行
c[j] = p[i + j] = q[i - j + n] = 0; // 恢复现场
}
}
int main()
{
cin >> n; // 输入棋盘大小
dfs(1); // 从第1行开始搜索
cout << ans << endl; // 输出解的总数
return 0;
}
【运行结果】
6
2 4 6 1 3 5
3 6 2 5 1 4
4 1 5 2 6 3
4
浙公网安备 33010602011771号