题目描述
Given n positive numbers, ZJM can select exactly K of them that sums to S. Now ZJM wonders how many ways to get it!
输入
The first line, an integer T<=100, indicates the number of test cases. For each case, there are two lines. The first line, three integers indicate n, K and S. The second line, n integers indicate the positive numbers. |
输出
For each case, an integer indicate the answer in a independent line. |
样例输入
1
10 3 10
1 2 3 4 5 6 7 8 9 10
样例输出
4
思路
综述
这个题有两种思路:
第一种(暴力点的):
需要从n个数中选出K个,构成和为S;
自然可以枚举n的所有子集,依次判定是否满足题意;
复杂度:O(K*2^n)
第二种:
可以类似隐式图的做法;
只不过这里的每个点是K维的;
利用DFS进行搜索,外加剪枝;
(这里的搜索类似维度的增加,从点的维度是0开始搜索,每到一个点判断是否加入该维度,加加加,加到K维,外加剪枝的条件)
剪枝条件:
条件1:节点的个数多于K且和不等于S,退出;
条件2:节点的个数未小于K但是和已经大于S,退出;
满足以上任意一个条件即可;
过程
Step1:输入
数组a存储的是n个数
用STL中的list链表ans来存储结果(单单就解答这个题目而言,没有必要存储所有的结果)
a = new int[n];
for (j = 0; j < n; j++)cin >> a[j];
list<int> ans;
Step2:搜索
其中tot是全局变量,不用担心形参的释放问题;
退出的条件:
ans链表的个数等于K,并且和为S
if (ans.size() == K && sum == 0) {
tot++;
return;
}
剪枝问题:
除了之前的两个外,加上了触碰到了a数组的外面,也就是搜索到n个数之外的。
if (i >= n) return;
if (sum < 0 || ans.size()> K) return;
每到一个点,两个方向,一是加上该点继续向下,另一个是不加上该点继续向下;
solve(i + 1, sum, ans);
ans.push_back(a[i]);
solve(i + 1, sum -= a[i], ans);
ans.pop_back();
总结
深入思考:
单单就这个题而言,没必要加上list;
可以在搜索的过程中,加上一个参数number(int)来记录当前的维度情况,也就是当前选择了多少点。
这样可以最大化的降低复杂度。
代码
#include <iostream>
#include <list>
using namespace std;
int tot = 0;
int K = 0;
int s;
int n;
int* a;
void solve(int i, int sum, list<int>& ans) {
//退出
if (ans.size() == K && sum == 0) {
tot++;
return;
}
//剪枝
if (i >= n) return;
if (sum < 0 || ans.size()> K) return;
//不选
solve(i + 1, sum, ans);
ans.push_back(a[i]);
//选
solve(i + 1, sum -= a[i], ans);
ans.pop_back();
}
int main() {
int t, i,j;
cin >> t;
for (i = 0; i < t; i++) {
cin >> n >> K >> s;
a = new int[n];
for (j = 0; j < n; j++)cin >> a[j];
list<int> ans;
//开始搜索
solve(0, s, ans);
cout << tot << endl;
tot = 0;
delete[]a;
}
}