B: 部分和问题***(注意部分和 ! = 任意子区间求和不一样)
描述 给你N个数,问你能不能从其中取出一些,让它们的和为K.
输入
第一行包括两个数,N,K,分别代表数字个数,以及和为K. 接下来N行,每行一个数字.
输出
如果能选出一些数和为K, 输出YE5, 否则,输出N0
样例
输入:
4 0
1 -1 2 3
输出:
YE5
输入:
2 2
1 -3
输出:
N0
本题求组合数和,注意pe之外,思维比较基础,办法很多,以下为利用dfs思想实现的一种办法
如果求组合数输出各种可能,直接递归是不行的,那样智能遍历,需要一个媒介数组,存放每种可能,因为要每次返回都要利用上一层的东西
观察 1 2 3 4 对于C(4,3)= C(n,r)
1 2 3
1 2 4
1 3 4
2 3 4
对于(a,b,c)型,最终在 首位 a = n-r+1 = 4-3+1 = 2 , 递归函数内部 c=n , it = r 时停止遍历,注意第47行的条件;
组合数比全排列难的地方就在于这个限制 "重复" 条件,首先,对于任何一层的组合,
- 遍历首位不会超过n-r+1
- 遍历尾部不会超过n,
- 遍历总数不会超过r
因此分为两个部分,dfs函数是对尾部(分支)的遍历 , 利用条件3约束, main函数内是对首位穷举, 方式是循环, 利用条件3约束
如果用bool 标记已经找过的数,思路和排列差不多,标记每次遍历到直接跳过就行了,返回的时候在函数末尾消除标记就可
(用全排列改写的组合数输出)
1 #include <iostream>
2 #include <cstring>
3 #include <queue>
4 #include <cstdio>
5 #include <cmath>
6 #include <map>
7 #include <algorithm>
8 typedef long long ll;
9 using namespace std;
10 int n;
11 int r;
12 int a[11];
13 bool x[11];
14 void f(int it,int num) {
15 //num表示此时已经排列了几层(栈的深度)
16 //it表示下一个要压入的数
17 a[num]=it;//入栈
18 x[it]=1;//mark
19 if(num==r) {
20 for(int j=1; j<=r; j++) {
21 cout<<a[j]<<" ";
22 }
23 printf("\n");
24 } else {
25 for(int i=it+1; i<=n; i++) {
26 if(x[i]==1)continue;
27 else f(i,num+1);
28 }
29 }
30 x[it]=0;
31 }
32
33 int main () {
34 cin>>n>>r;
35 memset(x,0,sizeof(x));
36 for(int i=1; i<=n-r+1; i++) {
37 f(i,1);
38 }
39
40 return 0;
41 }
进一步化简, 从num=0,开始记录,
1 #include <iostream>
2 #include <cstring>
3 #include <queue>
4 #include <cstdio>
5 #include <cmath>
6 #include <map>
7 #include <algorithm>
8 typedef long long ll;
9 using namespace std;
10 int n;
11 int r;
12 int a[11];
13 bool x[11];
14 void f(int it,int num) {
15 //num表示此时已经排列了几层(栈的深度)
16 //it表示下一个要压入的数
17 a[num]=it;//入栈
18 x[it]=1;//mark
19 if(num==r) {
20 for(int j=1; j<=r; j++) {
21 cout<<a[j]<<" ";
22 }
23 printf("\n");
24 } else {
25 for(int i=it+1; i<=n; i++) {
26 if(x[i]==1)continue;
27 else f(i,num+1);
28 }
29 }
30 x[it]=0;
31 }
32
33 int main () {
34 cin>>n>>r;
35 memset(x,0,sizeof(x));
36
37 f(0,0);
38
39
40 return 0;
41 }
但是如果找规律,就需要明确找的数都只能比上一层的大,然后在此基础上循环到恰好是r个
以下为— dfs 利用栈—实现的 输出n个数的 全组和
1 #include <iostream>
2 #include <cstring>
3 #include <queue>
4 #include <cstdio>
5 #include <cmath>
6 #include <map>
7 #include <algorithm>
8 typedef long long ll;
9 using namespace std;
10 const int m=25;
11 ll a[m];
12 bool t=0;
13 ll sum=0;
14 int n;
15 ll k;
16 //用数组下标输出组合数!!!
17 //C(n,k)=C(n=1,k-1)
18
19
20 //1 2 3 4 5 6
21 //5 22 2 4 6 8 10
22 //5 2 4 6 8 10
23 ll b[m];
24 //b[m]看作一个栈
25 int g=1;
26 ll r;
27
28 void dfs(int num,int it) {
29
30 b[it]=a[num];
31 if(it==r) {
32 for(int i=1; i<r; i++)printf("%lld ",b[i]);
33 printf("%lld\n",b[r]);
34 //cout<<g++<<" "<<it<<"\n";
35 } else {
36 for(int i=num+1; i<=n; i++) {
37 dfs(i,it+1);
38 }
39 }
40 }
41
42 int main() {
43
44 scanf("%d",&n);
45 for(int i=1; i<=n; i++)scanf("%lld",&a[i]);
46 for(r=1; r<=n; r++) {
47 for(int i=1; i<=n-r+1; i++) {
48 sum=0;
49 dfs(i,1);
50 }
51 }
52 puts("");
53
54 return 0;
55 }
View Code
从0开始更加简洁;
1 #include <iostream>
2 #include <cstring>
3 #include <queue>
4 #include <cstdio>
5 #include <cmath>
6 #include <map>
7 #include <algorithm>
8 typedef long long ll;
9 using namespace std;
10 const int m=25;
11 ll a[m];
12 bool t=0;
13 ll sum=0;
14 int n;
15 ll k;
16 //用数组下标输出组合数!!!
17 //C(n,k)=C(n-1,k-1)
18
19
20 //1 2 3 4 5 6
21 //5 22 2 4 6 8 10
22 //5 2 4 6 8 10
23 ll b[m];
24 //b[m]看作一个栈
25 int g=1;
26 ll r;
27
28 void dfs(int num,int it) {
29
30 b[it]=a[num];
31 if(it==r) {
32 for(int i=1; i<r; i++)printf("%lld ",b[i]);
33 printf("%lld\n",b[r]);
34 //cout<<g++<<" "<<it<<"\n";
35 } else {
36 for(int i=num+1; i<=n; i++) {
37 dfs(i,it+1);
38 }
39 }
40 }
41
42 int main() {
43
44 scanf("%d",&n);
45 for(int i=1; i<=n; i++)scanf("%lld",&a[i]);
46 for(r=1; r<=n; r++) {
47
48 dfs(0,0);
49
50 }
51 puts("");
52
53 return 0;
54 }
在这基础上,直接把每次得到的序列求和,可以完成这题,
1 #include <iostream>
2 #include <cstring>
3 #include <queue>
4 #include <cstdio>
5 #include <cmath>
6 #include <map>
7 #include <algorithm>
8 typedef long long ll;
9 using namespace std;
10 const int m=25;
11 ll a[m];
12 bool t=0;
13 ll sum=0;
14 int n;
15 ll k;
16 //用数组下标输出组合数!!!
17 //C(n,k)=C(n=1,k-1)
18
19
20 //1 2 3 4 5 6
21 //5 22 2 4 6 8 10
22 //5 2 4 6 8 10
23 ll b[m];
24 //b[m]看作一个栈
25 int g=1;
26 ll r;
27
28
29
30 void f(int no,int now) {
31 if(t==1)return;
32 b[now]=a[no];//更新入栈,
33 if(now==r) { //栈满检查
34 sum=0;
35 for(int i=1; i<=r&&t==0; i++) {
36 sum+=b[i];
37 if(sum==k)t=1;
38 }
39 } else {
40 for(int i=no+1; i<=n; i++) {
41 f(i,now+1);
42 }
43 }
44 }
45
46 int main() {
47
48 scanf("%d%lld",&n,&k);
49 for(int i=1; i<=n; i++)scanf("%lld",&a[i]);
50 for(r=1; r<=n; r++) {
51 for(int i=1; i<=n-r+1; i++) {
52 sum=0;
53 f(i,1);
54 }
55 }
56 if(t==1)cout<<"YE5";
57 else cout<<"N0";
58
59 puts("");
60
61
62 return 0;
63 }
View Code
当然,求和,其实是不需要求这个序列的,遍历每一种可能,之后不需要把序列存下来,直接检查和,就可以满足了,
不含媒介数组的办法(注意第 44 行,sum减回来当前值的操作在每次到底之后都要执行)
1 #include <iostream>
2 #include <cstring>
3 #include <queue>
4 #include <cstdio>
5 #include <cmath>
6 #include <map>
7 #include <algorithm>
8 typedef long long ll;
9 using namespace std;
10 const int m=25;
11 ll a[m];
12 bool t=0;
13 ll sum=0;
14 int n;
15 ll k;
16 //用数组下标输出组合数!!!
17 //C(n,k)=C(n=1,k-1)
18
19
20 //1 2 3 4 5 6
21 //5 22 2 4 6 8 10
22 //5 2 4 6 8 10
23 ll b[m];
24 //b[m]看作一个栈
25 int g=1;
26 ll r;
27
28 void f2(int no,int now) {
29 sum+=a[no];//更新入栈,
30 //cout<<"sum="<<sum<<" ";
31
32 if(sum==k||t==1) {
33 t=1;
34 return;
35 }
36 if(now<r) { //栈满,
37 //cout<<"sum="<<sum<<"\n";
38 //
39
40 for(int i=no+1; i<=n; i++) {
41 f2(i,now+1);
42 }
43 }
44 sum-=a[no];//弹出!!!小心少了这一步
45 }
46 int main() {
47
48 scanf("%d%lld",&n,&k);
49 for(int i=1; i<=n; i++)scanf("%lld",&a[i]);
50
51 for(r=1; r<=n; r++) {
52 for(int i=1; i<=n-r+1; i++) {
53 sum=0;
54 f2(i,1);
55 }
56 }
57 if(t==1)cout<<"YE5";
58 else cout<<"N0";
59 puts("");
60 return 0;
61 }
另外的写法——来源:
问题:求n个数中K个数的组合,假设函数原型为 int combination(int n,int k),其中 n的范围为 1……n,
例如:combination(5,3) 要求输出:543、542、541、531、532、521、432、431、421、321
如果输出时有用到数组,其空间需要在开始动态分配好,结束时释放。还是利用递归,关键:c(m,k) = c(m -1 , k- 1) + c(m - 2, k - 1) + ...+ c(k - 1.k - 1)
# include <stdio.h>
# define MAXN 100
int a[MAXN];
/**
* 组合问题
*问题描述:找出从自然数1、2、……、m中任取k个数的所有组合。
*/
void comb(int m, int k) {
int i, j;
for (i = m; i >= k; i--) {
a[k] = i;
if (k > 1)
comb(i - 1, k - 1);
else {
for (j=a[0];j>0;j--)
printf("%4d",a[j]);
printf("\n");
}
}
}
int main() {
a[0] = 3;
// int a[] = {1,2,3,4,5};
comb(5, 3);
return 0;
}
凡是含—输出该序列—的题目都需要一个 “栈” 存放结果,区别在于,他把上界n-r+1写进了递归函数中,
表示为 for (i = m; i >= k; i--),思维更复杂,这个每次递归的时候上界都会变,
另一种写法:也是利用01标记法,但是实现写了一个表 + bfs实现
同理也可以使用 01 标记+ dfs实现(待续)