题目链接
引用
慕明 AcWing 93. 优秀的非递归实现组合型枚举(Gosper’s Hack)
题目描述
从 1∼n 这 n 个整数中随机选出 m 个,输出所有可能的选择方案。
样例
输入格式
两个整数 n,m ,在同一行用空格隔开。
输出格式
按照从小到大的顺序输出所有方案,每行 1 个。
首先,同一行内的数升序排列,相邻两个数用一个空格隔开。
其次,对于两个不同的行,对应下标的数一一比较,字典序较小的排在前面(例如 1 3 5 7
排在 1 3 6 8
前面)。
数据范围
$n \gt 0$ ,
$0 \le m \le n$ ,
$n + (n − m) \le 25$
输入样例:
5 3
输出样例:
1 2 3
1 2 4
1 2 5
1 3 4
1 3 5
1 4 5
2 3 4
2 3 5
2 4 5
3 4 5
算法
(递归) $\mathcal O(C_{n}^{m})$
所谓的组合型枚举,就是枚举的结果与顺序无关,只与组合它的数有关。
看到这,都想到了高中的排列组合问题,没错,这就是典型的从n个中选m个的问题。那么怎么将它转换为代码呢
首先,可以画一下递归搜索树
由搜索树可以看出,只要我们按照升序枚举的话,所有的结果也是按照升序排序的,并且因为是与顺序无关,不需要去标记哪个数被用过,能不能枚举,只需要记录一下选择的数。
可以看出某一个点的子树其选过的数都是相同的,那么是不是可以记录选择的个数的同时记录一下从哪个数继续枚举下去。
纵观整棵树,不难发现根节点后面几颗子树会出现枚举不了的情况,并且随着n、m的增大,这种情况会越来越多。那有没有一种办法可以省去这些判断呢?
剪枝,dfs里非常常见,顾名思义就是将不需要的枝条剪去,也就是递归搜索树的不符合条件的子树或者分支。比如这题,如果当前选的数加上剩下可以选的数还是不足需要的数,那么直接结束这一分支的搜索。
时间复杂度 $\mathcal O(C_{n}^{m})$
加上剪枝的话,时间复杂度是 $\mathcal O(C_{n}^{m})$,为什么呢,因为加上剪枝的话,所有结果搜索的次数刚好是 $\mathcal O(C_{n}^{m})$
可能不是那么准确,不过应该就是这个范围
C++ 代码
#include <iostream>
using namespace std;
const int N = 35;
int n , m;
int st[N];
void dfs(int u , int start) {
if (u + n - start < m) return; // 剪枝 u - 1 + n - start + 1 < m
if(u > m) {
for(int i = 1; i <= m; i ++) cout << st[i] << " ";
cout << endl;
return;
}
for(int i = start; i <= n; i ++) {
st[u] = i;
dfs(u + 1 , i + 1);
st[u] = 0;
}
}
int main() {
cin >> n >> m;
dfs(1 , 1);
return 0;
}
Orz
%%%