深搜练习——生成组合数
0.总结
Get to the key point firstly, the article comes from LawsonAbs!
- 使用递归生成不重复数集合中的组合
- 主要思想就是分治法。 (选或不选) 的问题
- 不同于使用某个集合数生成全排列
- 无论采用哪种方式生成组合数,主要点就在于要记录上次选择的下标值,从上次的这个下标开始选 这样可以做到不重不漏。
1.要求
从一个有 n
个数的集合(无重复数字)中选择 m
个数的集合出来,要做到不重不漏。
2.思路
2.1分治
在这 n 个数中,每个数都有选或不选两种选择。于是问题就转换为:选择当前的数,并从剩下的数中选择 m-1
个 和 不选择当前的数,但从剩下的数中选择 m
个。这样再分别解决子问题(从剩余的t个数中选或者不选),就可以得到问题的解。
在递归的过程中,有一些可以稍微优化的方法,
- 只有当剩余的数大于m时,我们才有能力说不选当前的这个数,(否则后面的数都不够m个,怎么也凑不齐了)。
3.实现
3.1 方法1
#include<iostream>
using namespace std;
const int maxN = 10;
int n,m;//从n个数中选择m个
int arr[maxN];
int vis[maxN];
int choose[maxN];//选择的结果集合
//start 是开始选择的下标; cnt 表示选了多少个数
void dfs(int start,int cnt){
if(cnt == m){//已经选择出了 m 个
for(int k = 0;k< cnt;k++)
cout << choose[k]<<" ";
cout <<"\n";
return ;
}
/*1.选当前的数
*/
vis[start] = 1;
choose[cnt] = arr[start];
dfs(start+1,cnt+1);
vis[start] = 0;
/*2.若有资格不选当前的数
*/
if(n - start >= m-cnt)
dfs(start+1,cnt);
}
int main(){
cin >> n >>m;
for(int i = 1;i <= n;i++){
arr[i] = i;
}
dfs(1,0);
}
3.2 方法2
这个方法2是结合洛谷的一道习题【P1036 选数】给出的。
#include<iostream>
#include<cmath>
using namespace std;
const int N= 25;
int n,k,res ;//最终的结果数=》主要是为了去重
int vis[N],arr[N];//是否访问过; 原始数组
//判断num是否是素数
bool isPrime(int num){
for(int i =2;i<=sqrt(num);i++){
if(num % i == 0)
return false;
}
return true;
}
//选数的过程
//cnt表示挑了几个数;start表示需要从哪个位置开始选择; sum表示已挑过的数的和
void dfs(int cnt,int start,int sum){
if(cnt >= k){
if(isPrime(sum) ){
res++;
}
return ;
}
for(int i = start;i<n;i++){
if(vis[i] == 0){//可选
vis[i] = 1;
dfs(cnt+1,i+1,sum+arr[i]);
vis[i] = 0;
}
}
}
int main(){
cin >> n >> k;
for(int i = 0;i < n;i++){
cin >> arr[i];
}
dfs(0,0,0);
cout << res <<"\n";
}
测试用例如下:
4 1
2 3 4 5
4 2
2 3 4 5
4 3
3 7 12 19