洛谷T476751
从 1~n 这 n(n<16) 个整数中随机选取任意多个,输出所有可能的选择方案。
一个整数n。
每行一种方案。同一行内的数必须升序排列,相邻两个数用恰好1个空格隔开。对于没有选任何数的方案,输出空行。本题的输出顺序请参照样例。
3
3
2
2 3
1
1 3
1 2
1 2 3
对于给定的整数 n,数字范围是从 1 到 n。每个数字都有两种选择:选中或者不选,因此我们可以用二进制来表示选择状态。
例如,n = 3 时,我们有 8 个可能的选择状态,对应的输出如下:
采用深度优先搜索(dfs)的思想,使用递归,我们可以按顺序来尝试对每个数字做出选择。
在递归过程中,我们用 state 的二进制位来表示当前选中的数字。例如,如果 state = 101(二进制),则表示第 1 位和第 3 位被选中了,即选中了数字 1 和 3。
#include
using namespace std;
// 定义全局变量 n,用于存储用户输入的整数
int n;
// 递归函数 dfs 用于生成所有选择方案
void dfs(int u, int state) {
// 当 u 达到 n 时,表示已经遍历完所有可能的数位
if (u == n) {
// 输出当前选择方案中的所有选中的数
for (int i = 0; i < n; i++) {
if (state >> i & 1) // 检查第 i 位是否被选中
cout << i + 1 << " "; // 输出选中的数
}
cout << endl; // 输出换行
return; // 结束当前递归调用
}
// 不选择第 u 个数,继续递归
dfs(u + 1, state);
// 选择第 u 个数,使用位运算将第 u 位设为 1,继续递归
dfs(u + 1, state | (1 << u));
}
int main() {
// 读取用户输入的整数 n
cin >> n;
// 从第 0 个数开始,初始状态 state 为 0
dfs(0, 0);
return 0;
}
在代码中,我们使用了二进制的位运算,下面简单的介绍几个常用的位运算,特别是代码中用到的位移运算和按位与运算。
在二进制中,每一位(0 或 1)代表一个开关状态。位运算可以在二进制层面上直接操作这些开关状态,效率高且适合状态组合问题。
通过上面所介绍的二进制运算,就可以理解:
通过state | (1 << u),将 state 的第 u 位设置为 1,而 state 中的其他位保持不变(不会影响之前递归层的结果)。
通过state >> i & 1,用来比较二进制数state第i位的数字是否为1。
这样就可以用一个整数 state 来表示数的选择情况,它将多个布尔值整合到一个整数中,简化了代码结构。
由于每一层递归都可能选择或不选择当前数,所以会产生不同的选择方案。理解递归树的展开过程以及如何逐步构建出所有可能的组合方案是解决本题的关键。我们可以画一个递归树用来帮助我们理解代码执行流程:
dfs(0, 000)
/ \
不选1 -> dfs(1, 000) dfs(1, 001) <- 选1
/ \ / \
不选2 -> dfs(2, 000) dfs(2, 010) dfs(2, 001) dfs(2, 011) <- 选2
/ \ / \ / \ / \
不选3 -> dfs(3,000) dfs(3,100) dfs(3,010) dfs(3,110) dfs(3,001) dfs(3,101) dfs(3,011) dfs(3,111) <- 选3
| | | | | | | |
null 3 2 2,3 1 1,3 1,2 1,2,3
由于state打印时,是按照从小到大遍历的,所以输出的数自然是按照升序排列,满足题目要求。
有了对上面这题的理解,就能很容易理解下面这题~~
洛谷U113177
把 1~n 这 n(n<10) 个整数排成一行后随机打乱顺序,输出所有可能的次序。
一个整数n。
按照从小到大的顺序输出所有方案,每行1个。 首先,同一行相邻两个数用一个空格隔开。其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面。
3
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
#include
#include
using namespace std;
int n;
vector<int> path; // 用于存储当前排列路径
// 深度优先搜索函数
void dfs(int u, int state) {
// 递归结束条件:当前已选择的数字数等于 n
if (u == n) {
for (int i=0;i<n;i++) // 输出当前排列
cout << path[i] << ' ';
cout << endl;
return;
}
// 遍历 1 到 n 的所有数字,尝试将其加入排列
for (int i = 0; i < n; i++) {
// 检查数字 i+1 是否已被选中,如果已选则跳过
if (!(state >> i & 1)) {
path.push_back(i + 1); // 将数字 i+1 加入当前排列路径
dfs(u + 1, state | (1 << i)); // 更新状态并递归到下一层
path.pop_back(); // 回溯,移除当前选择的数字
}
}
}
int main() {
cin >> n;
dfs(0, 0); // 从第一个位置开始,初始状态为 0(未选中任何数字)
return 0;
}
递归函数 dfs(u, state)
if (u == n)
for (int i = 0; i < n; i++)
state | (1 << i)
path.push_back(i + 1) 和 path.pop_back()
dfs(0, 000) [path: []]
|
+----------------+----------------+
| |
选择 1 选择 2
| |
dfs(1, 001) [path: [1]] dfs(1, 010) [path: [2]]
| |
+----------+-----------+ +--------+----------+
| | | |
选择 2 选择 3 选择 1 选择 3
| | | |
dfs(2, 011) [path: [1, 2]] dfs(2, 101) [path: [1, 3]] dfs(2, 110) [path: [2, 3]]
| | | |
+------+-------+ +-------+-----+ +---+-----+ +---+-----+
| | | | | | | |
选择 3 回溯 选择2 回溯 选择3 回溯 选择1 回溯
1,2,3 1,3,2 2,1,3 2,3,1
以上两题用递归实现指数型和排列型枚举,主要理解了深度优先(dfs)的思想和递归回溯。