题解:洛谷 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

【核心思想】

  1. 问题分析:在 \(n \times n\) 棋盘上放置 \(n\) 个皇后,要求每行、每列、每条对角线(包括两条主对角线的所有平行线)上至多一个皇后。求所有解,输出前3个,最后输出解的总数。这是一个经典回溯(DFS)问题,关键在于用三个标记数组分别记录列、主对角线、副对角线的占用状态。

  2. 算法选择

    • 深度优先搜索(DFS):逐行放置皇后,每行尝试每一列
    • 状态标记数组
      • c[j]:第 \(j\) 列是否被占用
      • p[i+j]:主对角线(从左上到右下)是否被占用,同一主对角线上的点满足 \(i+j\) 为常数
      • q[i-j+n]:副对角线(从右上到左下)是否被占用,同一副对角线上的点满足 \(i-j\) 为常数(加 \(n\) 防止负数下标)
  3. 关键步骤

    • 初始化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,标记三个数组为1
        • dfs(i+1)
        • 恢复标记为0(回溯)
    • 输出 ans
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n!)\),最坏情况遍历所有排列,实际因剪枝大幅减少
    • 空间复杂度:\(O(n)\),标记数组和递归栈
  5. 回溯法的核心思想

    • 逐行决策:每行恰好放一个皇后,将二维问题降为一维序列(\(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
posted @ 2026-08-10 12:25  团爸讲算法  阅读(25)  评论(0)    收藏  举报