大家好哇^ _ ^
火星人问题(P1088)
一、next_permutation函数解析
1.1 基本定义与特性
next_permutation
是C++标准库<algorithm>
中的全排列生成函数,其核心功能是按照字典序生成给定序列的下一个排列。函数的两种形式:
bool next_permutation(BidirIt first, BidirIt last); // 默认使用<比较
bool next_permutation(BidirIt first, BidirIt last, Compare comp); // 自定义比较函数
- 返回值:存在更大排列时返回
true
,否则返回false
并重置为最小排列12 - 时间复杂度:O(n),n为序列长度
关键特性:
- 必须先将序列排序为最小字典序,才能生成所有排列
- 直接修改原序列,生成新排列
- 支持自定义比较规则处理特殊顺序(如大小写混合排序)
1.2 典型使用模式
生成全排列的标准模板:
sort(arr, arr+n); // 必须排序
do {// 处理当前排列
} while(next_permutation(arr, arr+n));
二、算法竞赛应用实例
2.1 火星人问题(P1088)
题目本质:给定排列,求其之后第M个字典序排列
核心代码:
int main() {int n, m;cin >> n >> m;vector<int> fingers(n);for(int i=0; i<n; i++) cin >> fingers[i];// 直接调用m次生成下一个排列for(int i=0; i<m; i++) next_permutation(fingers.begin(), fingers.end());// 输出结果for(int i=0; i<n; i++) cout << fingers[i] << " \n"[i==n-1];
}
关键点:
- 初始排列不需要排序,题目给定的就是当前排列状态
- 连续调用m次函数即可得到目标排列
三、排列型DFS的通用模板
当需要处理以下场景时,需手写DFS回溯算法:
- 元素有重复需要去重(如
[1,1,2]
的全排列) - 需要剪枝优化时间复杂度
- 题目要求输出特定格式(如按层分步输出)
模板题
通用模板代码:
vector<vector<int>> res;
vector<int> path;
vector<bool> visited;void dfs(int u, int n) {if(u == n) {res.push_back(path);return;}for(int i=0; i<n; i++) {if(!visited[i]) {path.push_back(nums[i]); // 选择当前元素visited[i] = true;dfs(u+1, n); // 递归下一层path.pop_back(); // 回溯visited[i] = false;}}
}// 调用方式:
sort(nums.begin(), nums.end()); // 处理重复元素时需要
visited.resize(nums.size(), false);
dfs(0, nums.size());
与库函数的对比:
特性 | next_permutation | DFS回溯 |
---|---|---|
时间复杂度 | O(n!) | O(n!) |
空间复杂度 | O(1) | O(n) |
处理重复元素 | 需要手动去重 | 可剪枝去重 |
适用数据规模 | n≤12 | n≤10 |
代码复杂度 | 极低 | 中等 |
四、实战技巧总结
- 字典序的本质:排列的比较规则类似于字符串比较,
213
>132
因为首位2>1 - 逆序检测:当
next_permutation
返回false时,说明当前是最大排列,此时序列被重置为升序 - 性能优化:当n较大时(如n>12),应避免使用全排列算法,考虑数学方法或动态规划
- 特殊排列处理:通过自定义比较函数可实现非标准字典序(如大小写字母混合排序)
选择建议:
当需要按顺序生成排列时,优先考虑next_permutation
当需要剪枝或处理复杂约束时,使用DFS
N较大时(如N>15),必须使用STL函数
通过合理选择库函数与手写算法,可以显著提升排列类问题的解题效率。建议在竞赛中优先使用
next_permutation
处理小规模数据,遇到需要剪枝或特殊处理的排列问题时再考虑DFS回溯算法。