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(" "); 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(" "); 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 ",b[r]); 34 //cout<<g++<<" "<<it<<" "; 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 }
从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 ",b[r]); 34 //cout<<g++<<" "<<it<<" "; 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 }
当然,求和,其实是不需要求这个序列的,遍历每一种可能,之后不需要把序列存下来,直接检查和,就可以满足了,
不含媒介数组的办法(注意第 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<<" "; 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 }
另外的写法——来源:https://blog.csdn.net/randyjiawenjie/article/details/6784355
问题:求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(" "); } } } 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实现 https://blog.csdn.net/cao2219600/article/details/79587306
同理也可以使用 01 标记+ dfs实现(待续)