【洛谷DFS算法】P1088火星人
给定 1~N 的初始排列,求其第 M 个字典序后继排列(初始排列为第 1 个)。通过 DFS 回溯生成排列,首次强制使用初始排列,后续按字典序枚举,利用num计数找到目标排列,核心在于统一 0/1 下标逻辑避免边界错误,适用于 M 较小(≤100)的场景,本质是字典序排列的顺序生成与计数。

【算法思路】
-
递归终止条件
◦ 首先检查 return0 标志,如果为 true,则直接返回。这是为了在找到目标排列后停止搜索。
◦ 当 x == n 时,表示已经处理完了所有的位置(因为是 0 - based 索引,处理了 0 到 n - 1 共 n 个位置),此时排列构建完成。
◦ 对于构建完成的排列,num 加 1,表示生成了一个新的排列。
◦ 如果 num 等于 m + 1,说明找到了目标排列(因为初始排列算第 1 个,往后数 m 个),此时输出这个排列,并将 return0 设置为 true,以终止后续的搜索。 -
排列构建过程(深度优先搜索部分)
◦ 当 num 为 0(即首次生成排列)时,按照初始排列来构建当前排列。通过获取 mars[x] 的值(这是初始排列中第 x + 1 个位置的数字,因为是 0 - based 索引,这里的 x 对应初始排列中的第 x + 1 个位置),然后检查这个数字是否未被使用(通过 st 数组)。如果未被使用,则将其标记为已使用(st[target] = true,这里的 target 就是 mars[x]),并将其放入当前构建的排列 arr 中(arr[x]=target),然后递归调用 dfs(x + 1) 处理下一个位置。在递归返回后,需要进行回溯操作,即将该数字标记为未使用(st[target]=false),以便在其他分支中可以再次使用。
◦ 当 num 不为 0 时(即不是首次生成排列),按照字典序生成后续排列。通过循环遍历数字 1 到 n(for(int i = 1; i <= n; i++)),对于每个未被使用的数字(if(!st[i])),将其标记为已使用,放入当前排列 arr 中,然后递归调用 dfs(x + 1),在递归返回后同样进行回溯操作,将数字标记为未使用并清空当前位置在 arr 中的值。
【代码示例】
#include <iostream>
#include <cstring>
using namespace std;
const int N = 10010;
int n, m;
int arr[N]; // 存储当前生成的排列,记录每一位的数字
int mars[N]; // 存储火星人输入的初始排列(题目中给定的手指排列顺序)
int num = 0; // 记录生成的排列序号,从输入排列开始计数
bool st[N]; // 标记数字是否已使用(true:已用;false:未用)
bool return0; // 终止标志,找到目标排列后设为true,提前结束搜索
// DFS函数:生成排列,x表示当前处理排列的第x位
void dfs(int x) {
if (return0) { // 若已找到目标排列,直接返回,避免无效计算
return;
}
if (x > n) { // 生成完整排列
num++; // 排列序号+1
if (num == m + 1) { // 找到目标排列(输入排列是第1个,往后数m个)
for (int i = 1; i <= n; i++) {
cout << arr[i];
if (i != n) cout << " "; // 控制输出格式,数字间用空格分隔
}
return0 = true; // 标记搜索结束
}
return;
}
for (int i = 1; i <= n; i++) {
if (!num) { // 首次生成排列(num=0),使用输入的初始排列
i = mars[x]; // 强制取初始排列中对应位置的数字
}
if (!st[i]) { // 数字i未使用
st[i] = true; // 标记为已用
arr[x] = i; // 将i放入当前排列的第x位
dfs(x + 1); // 递归处理下一位
st[i] = false; // 回溯:撤销标记,恢复数字i的未使用状态
arr[x] = 0; // 回溯:清空当前位置,恢复递归前状态
}
}
}
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> mars[i]; // 读取火星人输入的初始排列
}
dfs(1); // 从排列第1位开始,启动DFS生成排列
return 0;
}
注意:逻辑位置起始点的一致性:统一数组索引和排列数字,强制使用1-based下标
更多推荐



所有评论(0)