小韦老师@NOIP普及组-2001-数的计算

小韦老师@NOIP普及组-2001-数的计算

题目:

描述

我们要求找出具有下列性质数的个数(包含输入的自然数 n):

先输入一个自然数 n ( n<=1000),然后对此自然数按照如下方法进行处理:

  1. 不作任何处理;

  2. 在它的左边加上一个自然数,但该自然数不能超过原数的一半;

  3. 加上数后,继续按此规则进行处理,直到不能再加自然数为止。

输入

输入一个自然数。

输出

输出满足条件的数的个数。

输入样例1

6

输出样例1

6

提示

输入样例1 的 6 个数分别是:

6

16

26

126

36

136

来源

NOIP 普及组 2001年 第一题

题解:

破题

对于自然数 n,按照以下的方法进行出处理:

1.不作任何处理;
2.在它的左边加上一个自然数,但该自然数不能超过原数的一半;
3.加上数后,继续按此规则进行处理,直到不能再加自然数为止。
这里的此规则是指,这个新的数要将刚刚得到的数作为处理的对象再进行处理,
例如 n = 6:

1.先得到的数是 6

2.枚举 1~3(6 的一半,即为 3)

  1. 枚举 1,可以放在 6 的左边变成 16,1 已经不能再枚举小于 1 的一半的数

  2. 枚举 2,可以放在 6 的左边变成 26;2 还可以枚举 1,所以放在 26 的前面变成 126,现在 1 已经不可以再枚举

  3. 枚举 3,可以放在 6 的左边变成 36;3 还可以枚举 1,所以放在 26 的前面变成 136,现在 1 已经不可以再枚举

所以,得到了 6 个数分别是:6,16,26,126,36,136

方法一:递归

思路:
整体思路:

对于一个自然数 n 而言,得到它自己这 1 个数后,然后枚举 1~n/2 得到 n/2 个数,然后再对枚举 1~n/2 的每个数进行同样规则的枚举,所以原问题被分解成结构与原问题一致,但是规模变小的子问题,因此可以用递归。

具体步骤:

1.定义 n,并输入 n。

2.定义一个 cnt,作为计数器,用于记录结果。

3.调用自定义函数完成计算过程:

	// 函数功能:完成计算过程
	// 参数:n
	// 返回值:无 
	f(n);   

4.输出结果 f(n)。

5…实现递归函数 f(n):

    void f(int n) {
    	cnt++;  // 进来就加 1 (第一次进来的时候是加 n 自己) 
    	for (int i = 1; i <= n/2; i++) {  // 枚举 1~n/2 
    		f(i);  // 调用递归函数 
    	}
    }
完整代码:
#include <bits/stdc++.h>

using namespace std;

int cnt = 0;  // 计数器,用于记录结果 
void f(int n) {
	cnt++;  // 进来就加 1 (第一次进来的时候是加 n 自己) 
	for (int i = 1; i <= n/2; i++) {  // 枚举 1~n/2 
		f(i);  // 调用递归函数 
	}
}

int main() {
	
	// 定义 n,并输入 n 
	int n;
	cin >> n;
	// 函数功能:完成计算过程
	// 参数:n
	// 返回值:无 
	f(n);  // 调用自定义函数完成计算过程 
	// 输出结果 
	cout << cnt;

	return 0;
}

方法二:递推

思路:
整体思路:

从 n = 6 的例子中,我们可以发现,如果用 f[n] 表示自然数为 n 时,按照以上规则可以得到的数的数量(也即题目所求),则递推式为:

f[n] = 1 + f[1] + f[2] + f[3] +……+f[n/2](加 1 是加上自己)

边界条件为:

f[1] = 1 

f[1]~f[n/2] 为 1~n/2 在 n 的左边时能得到的数的数量。
例如:n = 6 时,

f[2] = f[1] + 1 = 2

f[3] = f[1] + 1 = 2

f[4] = f[1] + f[2] + 1 = 4

f[5] = f[1] + f[2] + 1 = 4

f[6] = f[1] + f[2] + f[3] + 1 = 1 + 2 + 2 + 1 = 6 
具体步骤:

1.定义数组 f,用来存储对应的自然数能得到的数的数量

	const int N = 1e3 + 10;
	int f[N];

2.定义 n,并且输入 n

3.处理边界条件, n = 1 时 f[1] 等于 1。

4.枚举 2~n,每次先把自己加上,然后根据递推式计算:

	for (int i = 2; i <= n; i++) {  // 枚举 1~n 
        f[i] = 1;  // 先把 i 自己加上 
        // 枚举 1~i/2,f[i] = f[1] + f[2] + f[3] + ……+ f[i/2] 
        for (int j = 1; j <= i/2; j++) {  
        	f[i] += f[j];   
        }
    }	

5.输出结果 f[n]。

完整代码:
#include <bits/stdc++.h>

using namespace std;

// 定义数组 f,用来存储对应的自然数能得到的数的数量 
const int N = 1e3 + 10;
int f[N];

int main() {
	
	// 定义 n,并且输入 n 
	int n;
	cin >> n;
	f[1] = 1;  // 边界条件, n = 1 时等于 1 
	for (int i = 2; i <= n; i++) {  // 枚举 2~n 
		f[i] = 1;  // 先把 i 自己加上 
		// 枚举 1~i/2,f[i] = f[1] + f[2] + f[3] + ……+ f[i/2] 
		for (int j = 1; j <= i/2; j++) {  
			f[i] += f[j];   
		}
	}	
	// 输出结果 
	cout << f[n];
	
	return 0;
}

方法三:动态规划

思路:
整体思路:

用 f(n) 来表示自然数为 n 时的结果

我们来观察以下的例子:

	n = 1,f(n) = 1
	n = 2,f(n) = 2
	n = 3,f(n) = 2
	n = 4,f(n) = 4
	n = 5,f(n) = 4
	n = 6,f(n) = 6 
	n = 7,f(n) = 6 
	n = 8,f(n) = 10 
	n = 9,f(n) = 10

通过观察,我们可以发现第 1 个规律,当 n 为奇数时,f(n) = f(n-1)(n > 1)

而 n 为偶数时有什么规律呢?我们以 n = 8 来观察:

1 18 28 128 38 138 48 148 248 1248 

对于前面的 1 18 28 128 38 138 而言,跟 n = 7 的情况是一样的:

	1 18 28 128 38 138
	1 17 27 127 37 137

而对于后面的 48 148 248 1248 则和 n = 4 的情况是一样的:

48 148 248 1248
4	14  24  124

所以我们得到第 2 个规律:

当 n 为偶数时,f(n) = f(n-1) + f(n/2)

具体步骤:

1.定义数组 f,用来存储对应的自然数能得到的数的数量

	const int N = 1e3 + 10;
	int f[N];

2.定义 n,并且输入 n。

3.处理边界条件 f[1] = 1。

4.枚举 2~n,若是计数项,则等于前一项的值;若是偶数项,则等于前一项加上自己的一半的那一
项的值。

    for (int i = 2; i <= n; i++) {
        if (i % 2 != 0) {  // 若是奇数项,则等于 f[i-1] 
        	f[i] = f[i-1];
        } else {  // 若是偶数项,则等于 f[i-1] + f[i/2] 
        	f[i] = f[i-1] + f[i/2];
        }
    }

5.输出结果。

完整代码:
#include <bits/stdc++.h>

using namespace std;

// 定义数组 f,用来存储对应的自然数能得到的数的数量 
const int N = 1e3 + 10;
int f[N];

int main() {

	// 定义 n,并且输入 n 
	int n;
	cin >> n;
	// 处理边界条件 
	f[1] = 1;
	// 枚举 2~n 
	for (int i = 2; i <= n; i++) {
		if (i % 2 != 0) {  // 若是奇数项,则等于 f[i-1] 
			f[i] = f[i-1];
		} else {  // 若是偶数项,则等于 f[i-1] + f[i/2] 
			f[i] = f[i-1] + f[i/2];
		}
	}
	// 输出结果 
	cout << f[n];
	
	return 0;
}

思考:

1°这三种方法你更喜欢哪一种方法?请说明理由

2°这三种的方法的优缺点各是什么?

3°这三种方法有什么相同点,有什么不同点?

4°你能这个题收获些什么?

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值