c++实现--递归选数问题

题意

给定一个数组,里面有n位正整数,要从这个数组里面选取K个数,使得它们的和为S,问有多少种可能的取法;

Input

第一行,一个整数T(T<=100),指示测试用例的数量。
对于每个情况,有两行。
第一行,三个整数表示n,K和S.其中K<=n<=16.
第二行n个整数表示n个元素的数组。
数据保证所有数字都可以以 32 位整数存储。

Output

对于每种情况,输出一个整数,代表可能的取法,独占一行。

Sample Input

1
10 3 10
1 2 3 4 5 6 7 8 9 10

Sample Output

4
//1+2+7=10
//1+3+6=10
//1+4+5=10
//2+3+5=10

思路

一般思路为三重循环暴力破解,但是n³复杂度明显过高;
首先,我们可以看出在一个数组中选择组合情况,是很明显的子集枚举问题,我们可以用递归处理。
还有,为了降低复杂度,我们可以在选择的时候做一些合理性剪枝,比如说在小于K个数的时候和已经大于S了,那么就没有必要去继续找了,此趟递归就可以结束了;又或者在递归的时候已经有了K个数,但是其和还小于S,那么就可以结束此趟递归。

过程

在遍历到每一个数a的时候,我们都有两种选择,就是这个数选或者不选:
如果选了,那么S=S-a;K=K-1;
如果不选,那么KS都不变;
具体过程看以下代码,有详细注释。

代码


#include<iostream>
#include<cstdio>
using namespace std;
int num[17];   //存储数列 
int count=0; //满足条件的组合数
void selectNumber(int i,int &n,int x,int &K,int S)
{//递归函数 枚举可能的组合   
//i 为数组的索引 n为数组的个数 x为当前选择数的个数 K为总的可以选的个数 S为需要到达的和
	if(S==0&&x==K)
	{//递归终点 S=0 且 当前的个数x=k
		count++;
		return ;
	}
	if(i>=n) return;   //遍历终点
	if(x>K||S<0) return ;   //剪枝 如果当前个数x大于K 总和<0 的时候
	
	selectNumber(i+1,n,x+1,K,S-num[i]);//选 
	//选择当前i索引点 继续遍历下个点i+1 当前节点个数+1 并且S减去这个当前值
	//下一次递归相当于 求 在剩下的数组中 求K-(x+1)个数 使得它们的和为S-num[i]
	selectNumber(i+1,n,x,K,S);//不选 	
	//不选当前i索引点 继续遍历下个点 x 不变 S不变
}
int main()
{

	int T;
	scanf("%d",&T);
	int n,K,S;
	while(T--)
	{
		count=0;
		scanf("%d %d %d",&n,&K,&S);
		for(int i=0;i<n;i++)
		{
			scanf("%d",&num[i]); 
		}  
		selectNumber(0,n,0,K,S);
		cout<<count<<endl;
	} 
	return 0;
}

总结

这道题是一道非常基础的递归枚举问题,在多加一些合理的剪枝,注意细节,就可以直接过。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值