很久之前看过这个题目,但是没有仔细整理,直到现在看基础才想到这两个题。这两个题非常经典也非常类似。接下来分别介绍。
部分和问题
题目描述
给定整数a1、a2、.......an,判断是否可以从中选出若干数,使它们的和恰好为K。
输入
首先,n和k,n表示数的个数,k表示数的和。
接着一行n个数。
(1<=n<=20,保证不超int范围)
输出
如果和恰好可以为k,输出“YES”,并按输入顺序依次输出是由哪几个数的和组成,否则“NO”
样例输入
4 13 1 2 4 7
样例输出
YES 2 4 7
思路:
很明显,这是一道简单的dfs搜索,直接理由dfs的定义解决,不过最难点是如何实现剪枝,减少不必要的时间浪费,这道题需要减掉的是
1.从当前状态如何转移都不会存在解
2.当sum超过k时,也没必要继续搜索
代码:
//没有剪枝的代码 #include<cstdio> #include<iostream> using namespace std; int n,k,a[22],b[22]; bool dfs(int x,int sum) //从左到右遍历一遍可得解 { if(sum>k) return false; if(x==n) return sum==k; //如果前n项计算过了,返回sum=k是否相等 if(dfs(x+1,sum)) { b[x]=0; //如果不加上a[x]的情况,标记为0; return true; } if(dfs(x+1,sum+a[x])) { b[x]=1; //如果加上a[x]的情况,标记为1; return true; } return false; } int main() { while(scanf("%d%d",&n,&k)!=EOF) { for(int i = 0; i<n; i++) scanf("%d",&a[i]); if(dfs(0,0)) { printf("YES\n"); for(int i=0; i<n; i++) if(b[i]) printf("%d ",a[i]); printf("\n"); } else printf("NO\n"); } return 0; }
和为sum的方法数
题目描述:
给定一个有n个正整数的数组A和一个整数sum,求选择数组A中部分数字和为sum的方案数。
当两种选取方案有一个数字的下标不一样,我们就认为是不同的组成方案。
输入:
输入为两行: 第一行为两个正整数n(1 ≤ n ≤ 1000),sum(1 ≤ sum ≤ 1000) 第二行为n个正整数A[i](32位整数),以空格隔开。
输出:
输出所求的方案数
样例输入
5 15 5 5 10 2 3
样例输出
4
思路一:
用递归加回溯的方法,找出数组的所有子集。
若子集和等于整数sum,则数组A中部分数字和为sum的方案数加一。
可优化的地方在子集当前和大于sum,则跳出该分支,因为数组A为正整数,之后的子集和只会越来越大。
这种方法缺点在于:时间复杂度大,为 O(2 ^ n) ,递归调用次数过多,容易爆栈。
#include<iostream> #include<vector> using namespace std; int n, sum, count = 0; void help(vector<int>& a, int pos, int part) { if (part == sum) count++; if (part > sum) return; for(int i=pos; i<n; i++) { part += a[i]; help(a, i+1, part); part -= a[i]; } } int main(){ cin>>n>>sum; vector<int> a(n); for(int i=0; i<n; i++) cin>>a[i]; help(a, 0, 0); cout<<count<<endl; return 0; }
思路二:
用动态规划,类似01背包问题,f(i , j )表示前i 个数中和为 j 的方案数, 则 若 j >= a[i], f ( i ,j) = f(i -1, j)+ f (i - 1,j - a[i] );
否则, f ( i ,j) = f(i -1, j)。
可优化地方:由于二维数组中,第i行 只与第 i - 1 行有关,所有我们若从 最后一列 开始更新数组,则可用一维数组来保存先前状态。
时间复杂度为:O( n * sum ) 。
#include<iostream> #include<vector> using namespace std; int main() { int n, sum; cin>>n>>sum; vector<long long> a(sum+1); vector<int> b(n); for(int i=0; i<n; i++) cin>>b[i]; a[0] = 1; for (int i=0; i<n; i++) for (int j=sum; j>=b[i]; j--) a[j] += a[j-b[i]]; cout<<a[sum]<<endl; return 0; }