问题描述
从一个大小为n的整数集中选取一些元素,使得它们的和等于给定的值T。每个元素限选一次,不能一个都不选。
输入格式
第一行一个正整数n,表示整数集内元素的个数。
第二行n个整数,用空格隔开。
第三行一个整数T,表示要达到的和。
第二行n个整数,用空格隔开。
第三行一个整数T,表示要达到的和。
输出格式
输出有若干行,每行输出一组解,即所选取的数字,按照输入中的顺序排列。
若有多组解,优先输出不包含第n个整数的;若都包含或都不包含,优先输出不包含第n-1个整数的,依次类推。
最后一行输出总方案数。
若有多组解,优先输出不包含第n个整数的;若都包含或都不包含,优先输出不包含第n-1个整数的,依次类推。
最后一行输出总方案数。
样例输入
5
-7 -3 -2 5 9
0
-7 -3 -2 5 9
0
样例输出
-3 -2 5
-7 -2 9
2
-7 -2 9
2
数据规模和约定
1<=n<=22
T<=maxlongint
集合中任意元素的和都不超过long的范围
T<=maxlongint
集合中任意元素的和都不超过long的范围
思路: 实现指数型枚举 选和不选两种情况
先上dfs朴素版代码
#include <iostream> #include <cstdio> #include <algorithm> #include <stack> #include <cstring> #include <iomanip> #include <cmath> using namespace std; const int N = 25; int n, m, ans; int a[N], st[N]; bool vis[N]; void dfs(int u, int step, int s) { if (u == -1) { if (s == m && step >= 1) { ans ++ ; for (int i = step - 1; i >= 0; i -- ) cout << st[i] << ' '; cout << endl; } return ; } // 不选这个数 dfs(u - 1, step, s); // 选这个数 vis[u] = true; st[step] = a[u]; dfs(u - 1, step + 1, s + a[u]); vis[u] = false; } int main() { cin >> n; for (int i = 0; i < n; i ++ ) cin >> a[i]; cin >> m; dfs(n - 1, 0, 0); cout << ans << endl; return 0; }
dfs+stl版本
#include <bits/stdc++.h> using namespace std; vector<int> ans; //存储选了哪些数 int nums[30]; //存储输入的整数集 int n, T; int cnt; //总方案数 void dfs(int id, int sum) { //id表示当前遍历到的数的下标,sum表示当前已经选择的数的总和 if (id == -1) { //如果搜索完了 if (sum == T && ans.size() > 0) { //如果和为T且至少选了一个数 for (int i = ans.size() - 1; i >= 0; i--) { cout << ans[i] << " "; } cout << endl; cnt++; } return; } dfs(id - 1, sum); //不选这个数 ans.push_back(nums[id]); //选这个数,把这个数加入到ans中 dfs(id - 1, sum + nums[id]); //dfs下一层 ans.pop_back(); //回溯 } int main() { cin >> n; for (int i = 0; i < n; i++) { cin >> nums[i]; } cin >> T; dfs(n - 1, 0); //从最后一个数开始往前搜索 cout << cnt << endl; return 0; }
二进制枚举 同样也是控制选与不选
二进制枚举子集:
在二进制的每一位上只存在两种可能,一种是0一种是1,我们把0看做是不选此数字,把1看成是选择此数字。
10011即为选择第一个和第四第五个数字,其余数字不选。
for ( int i = 0 ; i < ( 1 << n ) ; i++) ;
我们可以通过这个for循环得到每种情况
接下来就是判断每个位置上的数字情况了(利用按位与运算&)
按位与运算规则:0&0=0; 0&1=0; 1&0=0; 1&1=1; 左移<< : 1<<0=1(1); 1<<1=2(10); 1<<2=4(100); 1<<3=8(1000); 1<<4=16(10000); ... 1<<7=128(10000000); ...
我们可以通过将每一种情况对应的二进制数字与 1与不同的数字左移后得到的数字 进行按位与运算
10011 与 1左移零位后得到的数字进行按位与 10011&1 结果为1即第五位被选中 即是下标为0的数组元素被选中
10011 与 1左移一位后得到的数字进行按位与 10011&100 结果为1即第四位被选中 即是下标为1的数组元素被选中
10011 与 1左移二位后得到的数字进行按位与 10011&100 结果为0即第三位未被选中 即是下标为2的数组元素未被选中
10011 与 1左移三位后得到的数字进行按位与 10011&1000 结果为0即第二位未被选中 即是下标为3的数组元素未被选中
10011 与 1左移四位后得到的数字进行按位与 10011&10000 结果为1即第一位被选中 即是下标为4的数组元素被选中
将被选中的数字进行相加,看是否等于输入要求的数字,如果等于即输出结果,并将结果数加一。
将每一种情况进行此运算即可得到全部的结果。
#include <iostream> #include <cstdio> #include <algorithm> #include <stack> #include <cstring> #include <iomanip> #include <cmath> using namespace std; const int N = 25; int n, m, ans; int a[N], st[N]; bool vis[N]; int main() { cin >> n; for (int i = 0; i < n; i ++ ) cin >> a[i]; cin >> m; for (int i = 0; i < (1 << n); i ++ ) // 从0~2^n-1个状态 { int num = 0; for (int j = 0; j < n; j ++ ) // 遍历二进制的每一位 if (i >> j & 1) // 判断二进制第j位是否存在 num += a[j]; // 如果存在输出第j个元素 if (num == m && i != 0) // 不存在都不选的情况 所以i!=0 { for (int j = 0; j < n; j ++ ) if (i >> j & 1) cout << a[j] << ' '; cout << endl; ans ++ ; } } cout << ans << endl; return 0; }