子集和数问题(回溯法)

【问题描述】给定一个n个整数的集合X={x1,x2,…xn}(X中可能包含重复元素)和整数y,找出和等于y的X的子集Y。例如说,如果X={10,30,20,60,40,50},和y=60,则有4种不同的解,他们分别是{10,20,30},{10,50},{20,40},{60}。
【输入形式】输入的第1行包含两个整数n和y,分别表示集合X的长度和目标整数y。接下来1行包含n个整数(整数之间以空格分割),表示X中的n个元素。
【输出形式】输出1或0,若存在解,输出1,不存在则输出0。
【样例输入】

6 60

10 30 20 60 40 50
【样例输出】

1

题解:

解向量:
此题的解向量,严格来说,有两种。以题干中的例子 W W W={10,30,20,60,40,50}来说明,一种是题干中所给出的变长解{10,20,30},{10,50},{20,40},{60};另一种就是使用布尔向量表示的定长解 x 1 x_1 x1={1,1,1,0,0,0}, x 2 x_2 x2={1,0,0,0,0,1}, x 3 x_3 x3={0,0,1,0,1,0}, x 4 x_4 x4={0,0,0,1,0,0},0和1也就表示的是此位置的数选或者不选。本题解采用的是定长解向量。

规范函数:
w [ i ] w[i] w[i]为第 W W W中的第 i i i个数,则有 ∑ w [ i ] ∗ x [ i ] = y \sum w[i]*x[i]=y w[i]x[i]=y,说人话的话,由于 x [ i ] x[i] x[i]的取值只有0或者1,因此若 x [ i ] x[i] x[i]为0,也就是第 i i i个数没有被选中时, w [ i ] ∗ x [ i ] w[i]*x[i] w[i]x[i]就为0,所以前面的式子的意思就是所有的解必须满足选中的 w [ i ] w[i] w[i]求和等于 y y y

其实我们明确了解向量和规范函数之后就可以暴力地写一个深搜了,把所有可能地解都枚举一遍,只要找到满足规范函数的一个解向量就 r e t u r n return return

bool flag=false;
void dfs(int k, int sum){
	if(k>n){
		return;
	}
	if(sum==y){
		flag=true;
		return;
	}
	//选第k个数的情况
	x[k]=1;
	dfs(k+1,sum+w[k]);
	//不选第k个数的情况
	x[k]=0;
	dfs(k+1,sum);
}

虽然这样写,思路很简单,但是时间复杂度高达 2 n 2^n 2n(每个数有2种选择,选或者不选,组合起来总的选择数就为 2 n 2^n 2n),而且做了很多没有必要的搜索,因此,有必要进行剪枝。

  • 由于此题中的 w w w集合中元素的顺序对我们解决问题不会产生影响,我们首先对输入的 w w w数组进行从小到大的排序。
  • 我们为dfs函数的参数添加两个形参, s s s表示前 k − 1 k-1 k1个数的范围内选中的数的和, r r r表示还未搜索的数之和,显然, r + s = s u m r+s=sum r+s=sum总是成立的
  • 当我们考虑选中第 k k k个数的时候,我们预想一下,如果加上第 k + 1 k+1 k+1个数的话, s s s的值超过了 y y y,那么其实我们就没有必要选第 k k k个数了,因为继续搜索下去不可能搜索到解。而且我们事先已经排好了序,如果加上 w [ k + 1 ] w[k+1] w[k+1]大于 y y y,那加上 w [ k + 2 ] w[k+2] w[k+2]肯定也是大于 y y y的,因此只需判断是否满足 s + w [ k ] + w [ k + 1 ] < = y s + w[k] + w[k + 1] <= y s+w[k]+w[k+1]<=y
  • 当我们考虑不选第 k k k个数的时候,我们需要考虑剩余的数之和 r r r和目前的 s s s加起来能不能达到 y y y,如果不能,说明剩下的数根本不够用,也不可能产生解。除此之外,假如剩下的数足够选,这个时候同样需要判断加上 w [ k + 1 ] w[k+1] w[k+1]会不会大于 y y y,一旦大于了,就没有搜索下去的必要了。

代码实现:

#include <iostream>
#include <algorithm>
using namespace std;
int n, y, w[1001], sum = 0, x[1001];
set<string> vis;
//子集和数问题

bool flag = false;
void dfs(int k, int s, int r) {
    if (k > n) {
        return;
    }
    x[k] = 1;
    if (s + w[k] == y) {
        flag = true;
        return;
    }
    else {
        if (s + w[k] + w[k + 1] <= y) {
            dfs(k + 1, s + w[k], r - w[k]);
            x[k] = 0;
        }
        if (s - w[k] + r >= y && s + w[k + 1] <= y) {
            x[k] = 0;
            dfs(k + 1, s, r);
        }
    }
}

int main()
{
    cin >> n >> y;
    for (int i = 1; i <= n; i++) {
        cin >> w[i];
        sum += w[i];
    }
    if (sum >= y) {
        sort(w + 1, w + 1 + n);
        dfs(1, 0, sum);
        if (flag)
            cout << 1;
        else {
            cout << 0;
        }
    }
    else {
        cout << 0;
    }
    return 0;
}
  • 0
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值