试题 算法训练 和为T -> dfs 二进制枚举

问题描述
  从一个大小为n的整数集中选取一些元素,使得它们的和等于给定的值T。每个元素限选一次,不能一个都不选。
输入格式
  第一行一个正整数n,表示整数集内元素的个数。
  第二行n个整数,用空格隔开。
  第三行一个整数T,表示要达到的和。
输出格式
  输出有若干行,每行输出一组解,即所选取的数字,按照输入中的顺序排列。
  若有多组解,优先输出不包含第n个整数的;若都包含或都不包含,优先输出不包含第n-1个整数的,依次类推。
  最后一行输出总方案数。
样例输入
  5
  -7 -3 -2 5 9
  0
样例输出
  -3 -2 5
  -7 -2 9
  2
数据规模和约定
  1<=n<=22
  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;
}

 

猜你喜欢

转载自www.cnblogs.com/zbx2000/p/12757455.html
今日推荐