P1036 [NOIP2002 普及组] 选数

[NOIP2002 普及组] 选数

——递归

题目描述

已知 n n n 个整数 x 1 , x 2 , ⋯   , x n x_1,x_2,\cdots,x_n x1,x2,,xn,以及 1 1 1 个整数 k k k k < n k<n k<n)。从 n n n 个整数中任选 k k k 个整数相加,可分别得到一系列的和。例如当 n = 4 n=4 n=4 k = 3 k=3 k=3 4 4 4 个整数分别为 3 , 7 , 12 , 19 3,7,12,19 3,7,12,19 时,可得全部的组合与它们的和为:

3 + 7 + 12 = 22 3+7+12=22 3+7+12=22

3 + 7 + 19 = 29 3+7+19=29 3+7+19=29

7 + 12 + 19 = 38 7+12+19=38 7+12+19=38

3 + 12 + 19 = 34 3+12+19=34 3+12+19=34

现在,要求你计算出和为素数共有多少种。

例如上例,只有一种的和为素数: 3 + 7 + 19 = 29 3+7+19=29 3+7+19=29

输入格式

第一行两个空格隔开的整数 n , k n,k n,k 1 ≤ n ≤ 20 1 \le n \le 20 1n20 k < n k<n k<n)。

第二行 n n n 个整数,分别为 x 1 , x 2 , ⋯   , x n x_1,x_2,\cdots,x_n x1,x2,,xn 1 ≤ x i ≤ 5 × 1 0 6 1 \le x_i \le 5\times 10^6 1xi5×106)。

输出格式

输出一个整数,表示种类数。

样例 #1

样例输入 #1

4 3
3 7 12 19

样例输出 #1

1

提示

【题目来源】

NOIP 2002 普及组第二题

基本思路

  1. 转换成旧问题

可以将这道题想象成n个数里选k个数的全排列问题只不过这道题要我们求每组 全排列 并判断时候为 素数

#include <bits/stdc++.h>
using namespace std ;
int n , k ;
long long ans ;
int x[50] ;
int v[50];
bool pd ( int ) ;
void f ( int , int );
int main ( ){
	cin >> n >> k ;
	for ( int i = 1 ; i <= n ; ++i ){
		cin >> x[i] ;
	}
	f( 0 , 0 ) ;
	cout <<ans ;
	return 0 ;
}
bool pd ( int t ){	//判断是否为素数
	if ( t == 2 ) return true ; 
	if ( t == 1 ) return false ;
	for ( int i = 2 ; i <= sqrt ( t )  ; ++i ){
		if ( t % i == 0) return false ;
	}
	return true ;
}
void f ( int sum , int a ){//递归函数
// a为已经选了几个数 , sum为这几个数的和
	if ( a == k ){//选完
		if ( pd(sum) ){//判断
			++ans ;//记录答案
		}
		return ;//返回
	}
	for ( int i = b ; i <= n ; ++i ){
		if (v[i]==0){//是否被选过
			v[i] = 1 ;
			f(sum+x[i] , a+1 ) ;//递归
			v[i] = 0 ;
			}
	}
	return ;
}

最后
在这里插入图片描述【奔溃】Bye(开玩笑

  1. 去重

最后发现 没有去重,有重复答案,so , ans++ 会重复多次
怎么办?????????????

答案是:不降原则

不降原则是个神马意思呢

举个例子:
比如说在6里面随便选5个数,那么选法都是什么呢?
瞎枚举?
12345
12346
前两个还不会弄混
然后很可能就乱了
少点数可能不会乱
但是多了就不好整了
比如说在100里随便选50个数。
1 2 3 4 5 6 7 8 9 10 11 12…
Die.
所以我们可以运用不降原则:
保证枚举的这些数是升序排列
其实真正的不降原则还可以平
比如 1 2 2 3 3 4…
但是请注意这道题也不能平
否则就有重复数字了

拿6个里面选3个举例子
1 2 3
1 2 4
1 2 5
1 2 6
第一轮枚举完毕。
第二个数加一
1 3 ?
这个“?”应该是4,因为是升序排列
1 3 4
1 3 5
1 3 6
接着,就是这样
1 4 5
1 4 6
1 5 6
第一位是1枚举完毕
第一位是2呢?
2 3 4
2 3 5
2 3 6
2 4 5
2 4 6
2 5 6
就是这样的,枚举还是蛮清晰的吧
以此类推…
3 4 5
3 4 6
3 5 6
4 5 6
然后就枚举不了了,结束。
所以说,这样就可以避免判重了。

知道了不降原则,咱们再来see see 代码

Code

#include <bits/stdc++.h>
using namespace std ;
int n , k ;
long long ans ;
int x[50] ;
bool pd ( int ) ;//判断素数
void f ( int , int , int  );//递归(算法主体)
int main ( ){
	cin >> n >> k ;
	for ( int i = 1 ; i <= n ; ++i ){
		cin >> x[i] ;
	}
	f( 0 , 0 , 1 ) ;
	cout <<ans ;//输出答案
	return 0 ;
}
bool pd ( int t ){	//判断素数
	if ( t == 2 ) return true ; 
	if ( t == 1 ) return false ;
	for ( int i = 2 ; i <= sqrt ( t )  ; ++i ){
		if ( t % i == 0) return false ;
	}
	return true ;
}
void f ( int sum , int a , int b ){
// a为已经选了几个数 , sum是这几个数的和 , b为了去重(不降原则)
	if ( a == k ){//递归边界
		if ( pd(sum) ){//判断
			++ans ;//记录答案
		}
		return ;
	}
	for ( int i = b ; i <= n ; ++i ){
			f(sum+x[i] , a+1 ,) ;
			//———> “?” 应该填什么?提示:不降原则
	}
	return ;
}

祝大家AC快乐

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值