Bootstrap

2002 CSP-J/NOIP-J复赛真题精选题解(有代码,行行代码有注释)——秒杀T1 买级数求和,T2 选数,T3 产生数,T4 过河卒

[NOIP2002 普及组] 级数求和

题干

题目描述

已知: S n = 1 + 1 2 + 1 3 + … + 1 n S_n= 1+\dfrac{1}{2}+\dfrac{1}{3}+…+\dfrac{1}{n} Sn=1+21+31++n1。显然对于任意一个整数 k k k,当 n n n 足够大的时候, S n > k S_n>k Sn>k

现给出一个整数 k k k,要求计算出一个最小的 n n n,使得 S n > k S_n>k Sn>k

输入格式

一个正整数 k k k

输出格式

一个正整数 n n n

样例 #1

样例输入 #1
1
样例输出 #1
2

提示

【数据范围】

对于 100 % 100\% 100% 的数据, 1 ≤ k ≤ 15 1\le k \le 15 1k15

【题目来源】

NOIP 2002 普及组第一题

思路

这是第一题,所以比较简单。
看到数据范围,我们可以发现, k k k 较小, 1 ≤ k ≤ 15 1\le k \le 15 1k15,所以我们可以直接暴力枚举。
而怎么存储 1 / i 1 / i 1/i 的值呢?我们只需要使用 d o u b l e double double 即可,它是一种浮点数(小数)类型,用于进行小数计算。
再用一个 d o u b l e double double 变量 a n s ans ans 去存储 1 − i 1-i 1i 之间 1 / i 1 / i 1/i 的和,每增加一次就判断一次它是否大于 k k k ,如果大于 k k k ,输出当前的 i i i 即可。
如: k = 1 k = 1 k=1
i = 1 : a n s + = 1 / 1 ; ( a n s = 1 ) a n s = 1 = 1 i = 1 : ans += 1 / 1 ; (ans = 1) ans = 1 = 1 i=1:ans+=1/1;(ans=1)ans=1=1
i = 2 : a n s + = 1 / 2 ; ( a n s = 1.5 ) a n s = 1.5 > 1 i = 2 : ans += 1 / 2 ; (ans = 1.5) ans = 1.5 > 1 i=2:ans+=1/2;(ans=1.5)ans=1.5>1
输出答案为: 2 输出答案为:2 输出答案为:2

代码

#include <stdio.h>
double ans ;
int k,i ;
int main(){
	scanf("%d",&k) ;
	//输入
	while(ans <= k) ans += 1.0 / (++ i) ;
	//求和直到ans > k
	printf("%d",i) ;
	//输出i
	return 0 ;
}

[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 普及组第二题

思路

观察范围可得:输入的数据一般,数据个数较小。
所以我们可以直接模拟在 n n n 个数里面取 k k k 个数的情况,再判断这种情况所求出来的和是不是质数即可。
为了避免重复,我们可以使用“单向选取法”,即确定了前面有哪些数是取的以后,就不在改变前面的选择,而通过后面数的选与不选求出更多情况。
如:
4 4 4 2 2 2
3 3 3 7 7 7 12 12 12 19 19 19
确定选 3 3 3 以后,还可选 7 7 7 12 12 12 19 19 19
若不选 3 3 3 且确定选 7 7 7 以后,还可选 7 7 7 12 12 12 19 19 19
若不选 3 、 7 3、7 37 且确定选 12 12 12 以后,还可选 19 19 19
若不选 3 、 7 、 12 3、7、12 3712,因为只有一个数 19 19 19 了,所以没有合法的情况。

代码

#include <bits/stdc++.h>
using namespace std ;
bool is_prime(int t){
	if(t < 2)return false ;
	//如果是0、1或负数,则不是质数
	if(t < 4)return true ;
	//如果是2、3则是质数
	if((t % 6) != 1 && (t % 6) != 5)return false ;
	//如果 (t % 6) 为 0 2 4 时为 2 的倍数
	//如果 (t % 6) 为 3 时为 3 的倍数
	//所以一个质数(除2、3外),取余6的结果只能为1、5
	const int end = sqrt(t) + 1 ;
	//一个合数最大的质因子只能为它的开平方
	for(int i = 2;i < end;++ i)
		//循环判断这个数是否有除1、自身外的因子
		if(!(t % i))return false ;
		//如果有,返回 false
	return true ;
	//否则,返回true
}
int x[25],n,k,ans,all ;
//变量定义
void hs(int pos,int num,int s){
	//表示当前要判断选不选的数,已经选了的个数,和当前的和
	if(num == k){
		//如果个数满了,则判断是否为质数
		if(is_prime(s))++ ans ;
		//如果是质数,答案加1
		return ;
		//返回
	}else if(pos > n) return ;
	//如果数没了,直接返回
	hs(pos + 1,num,s) ;
	//这个数不选,直接跳过
	hs(pos + 1,num + 1,s + x[pos]) ;
	//选这个数,当前个数加一,总数加一
	return ;
}
int main(){
	scanf("%d%d",&n,&k) ;
	//输入总个数及需要选出的个数
	for(int i = 1;i <= n;++ i)scanf("%d",&x[i]) ;
	//输入各个数
	hs(1,0,0) ;
	//从第一个数开始选起
	printf("%d",ans) ;
	//输出答案
	return 0 ;
}

[NOIP2002 普及组] 产生数

题干

题目描述

给出一个整数 n n n k k k 个变换规则。

规则:

  • 一位数可变换成另一个一位数。
  • 规则的右部不能为零。

例如: n = 234 , k = 2 n=234,k=2 n=234,k=2。有以下两个规则:

  • 2 ⟶ 5 2\longrightarrow 5 25
  • 3 ⟶ 6 3\longrightarrow 6 36

上面的整数 234 234 234 经过变换后可能产生出的整数为(包括原数):

  • 234 234 234
  • 534 534 534
  • 264 264 264
  • 564 564 564

4 4 4 种不同的产生数。

现在给出一个整数 n n n k k k 个规则。求出经过任意次的变换( 0 0 0 次或多次),能产生出多少个不同整数。

仅要求输出个数。

输入格式

第一行两个整数 n , k n,k n,k,含义如题面所示。

接下来 k k k 行,每行两个整数 x i , y i x_i,y_i xi,yi,表示每条规则。

输出格式

共一行,输出能生成的数字个数。

样例 #1

样例输入 #1
234 2
2 5
3 6
样例输出 #1
4

提示

对于 100 % 100\% 100% 数据,满足 n < 1 0 30 n \lt 10^{30} n<1030 k ≤ 15 k \le 15 k15

【题目来源】

NOIP 2002 普及组第三题

思路

因为一个个位数最多有 10 10 10 种情况,所以一个数字能转换成的数字最多只有 10 10 10 种情况,因而可以用一个函数来计算这个数字所能转换成的数字数(包括自己)。过程如下:
首先 将一个大小为 10 10 10 的标记数组全部设为 f a l s e false false
接着,设计一个递归函数:
( 1 ) (1) (1) 通过参数传入当前已经转换成的数字,如果这个数字已经统计过了,直接返回
( 2 ) (2) (2) 否则,标记该数字为 t r u e true true ,即原数字可以转换到该数字,随后判断当前数字能转换为什么数字,将再次转换的的数字重复第一步的操作,标记数组不清零(即统计能间接转换到的数字)
( 3 ) (3) (3) 统计标记数组中有多少个数字标记成了 t r u e true true ,结果就是 t r u e true true 的个数
同时原数字中的一位数字也是最多对应十种情况,所以最多有 30 ∗ 10 = 100 30*10 = 100 3010=100 位数字,而原数字的大小也超过了 l o n g l o n g long long longlong 的最大范围,所以只能使用字符串来存储原数字,再使用大数算法计算原数各个位的可能性相乘的结果即可(高精乘低精)

代码

#include <bits/stdc++.h>
using namespace std ;
char s[35] ;
//用来储存原数的字符串
int k,times[10],x,y ;
//一些变量
bool all[10],a[10][10] ;
//一些变量
int ans[305],size = 1 ;
//储存答案的大数和大数的当前长度
void func(int which){
	if(all[which])return ;
	//如果当前数字已经标记了,则直接返回
	all[which] = true ;
	//否则直接标记
	for(int i = 0;i < 10;++ i)
		//标记能够间接转换的数字
		if(a[which][i])func(i) ;
	//返回
	return ;
}
int get(int now){
	//如果当前数字的答案已经计算过了,则直接返回,不重复计算
	if(times[now])return times[now] ;
	//清零标记数组
	for(int i = 0;i < 10;++ i)all[i] = false ;
	//开始标记
	func(now) ;
	//标记完成,统计能够转换成的数字的个数
	for(int i = 0;i < 10;all[i] = false,++ i)
		//如果一个数字标记到了,则统计上
		if(all[i])++ times[now] ;
	//返回结果
	return times[now] ;
}
int main(){
    scanf("%s%d",s,&k) ;
    //输入变量
    while(k --)scanf("%d%d",&x,&y),a[x][y] = 1 ;
    //输入转换关系
    ans[0] = 1 ;
    //大数类初始化
    for(int i = 0;s[i];++ i){
    	for(int o = 0;o < size;++ o)
    		ans[o] *= get(s[i] - '0') ;
    	//每一位乘以这一位的可能的情况数
    	for(int o = 0;o < size;++ o)
    		//进位操作
    		ans[o + 1] += ans[o] / 10,ans[o] %= 10 ;
    	//进位操作
    	while(ans[size])ans[size + 1] += ans[size] / 10,ans[size ++] %= 10 ;
	}
	//反向输出大数类,正向输出答案
    for(int i = size - 1;i >= 0;-- i)
    	//一位一位输出
    	putchar(ans[i] + '0') ;
    return 0 ;
}

[NOIP2002 普及组] 过河卒

题干

题目描述

棋盘上 A A A 点有一个过河卒,需要走到目标 B B B 点。卒行走的规则:可以向下、或者向右。同时在棋盘上 C C C 点有一个对方的马,该马所在的点和所有跳跃一步可达的点称为对方马的控制点。因此称之为“马拦过河卒”。

棋盘用坐标表示, A A A ( 0 , 0 ) (0, 0) (0,0) B B B ( n , m ) (n, m) (n,m),同样马的位置坐标是需要给出的。

现在要求你计算出卒从 A A A 点能够到达 B B B 点的路径的条数,假设马的位置是固定不动的,并不是卒走一步马走一步。

输入格式

一行四个正整数,分别表示 B B B 点坐标和马的坐标。

输出格式

一个整数,表示所有的路径条数。

样例 #1

样例输入 #1
6 6 3 3
样例输出 #1
6

提示

对于 100 % 100 \% 100% 的数据, 1 ≤ n , m ≤ 20 1 \le n, m \le 20 1n,m20 0 ≤ 0 \le 0 马的坐标 ≤ 20 \le 20 20

【题目来源】

NOIP 2002 普及组第四题

思路

这一题就是一个标准 d p dp dp 问题。
这一题我们可以发现,如果一个点被马控制了的话,那么它的值就会为 0 0 0 (终点、起点除外)
否则,它的值为走到它上方格子的情况数再加上走到它下方格子的情况数。 ( 状态转移方程 ) (状态转移方程) (状态转移方程)
而判断一个格子 ( i , o ) (i,o) i,o 是否是码的控制点,有两种情况:(终点、起点除外)
( 1 ) (1) 1 该点就是马所在的点
( 2 ) (2) 2 该点的纵坐标和横坐标与纵坐标和横坐标,其中一个相差 1 1 1 ,其中一个相差 2 2 2 。则它们的曼哈顿距离(纵坐标之差与横坐标之差的和)一定为3,且纵、横坐标均不相等

代码

#include <stdio.h>
typedef long long ll ;
ll abs(ll x){ return (x < 0) ? (-x) : (x) ; }
//取绝对值
ll n,m,x,y,a[25][25] ;
//一些变量
int main(){
    scanf("%lld%lld%lld%lld",&n,&m,&x,&y) ;
    //输入变量
    a[0][0] = 1 ;
    //起点不能被控制
    for(ll i = 0;i <= n;++ i)
    	for(ll o = 0;o <= m;++ o){
    		if(!i && !o)continue ;
    		//起点不能被控制
    		if(!(((i == x && o == y)) || 
				(((abs(i - x) + abs(o - y)) == 3) && (i != x) && (o != y)))){
				//如果该点不被控制
				if(i)a[i][o] = a[i - 1][o] ;
				//如果有上点,则增加上点的情况数
				if(o)a[i][o] += a[i][o - 1] ;
				//如果有左点,则增加左点的情况数
			}
		}
	printf("%lld",a[n][m]) ;
	//输出走到终点的情况数
    return 0 ;
}

尾声

又成功地“秒杀”了一年的CSP/NOIP,祝读者水平高升,参加竞赛都能“秒杀”取得好成绩!

;