【OJ】洛谷——P1028 数的计算

题目描述

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

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

  1. 不作任何处理;

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

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

输入格式

自然数 n n n,且 n ≤ 1000 n\leq1000 n1000

输出格式

1个整数,表示具有该性质数的个数

输入输出样例

输入

6

输出

6

解释:满足的数分别为6,16,26,126,36,136


1.首次尝试

解法一 递归

显然,最简单直接的做法就是递归(将一个大问题规约成更小规模的子问题,通过子问题的求解完成原问题)
假设:
当输入为4时,满足的其性质的数有——4本身,加上其规模 1 / 2 ^1/_2 1/2的子问题解的个数,即 4, 24, 124
根据我们的分析,算法的也就不难写出来了,递归代码如下

int fun(int n) {	//版本一 递归
	int cnt = 0;
	for(int i=1; i<=n/2; i++) {
		cnt += fun(i);
	}
	return cnt+1;
}

2.超时

递归的方法虽然简单直观,但是提交没过——超时了,递归深度太深导致爆栈了,这时候我们就需要思考能不能改进?能不能进一步优化?

启发

还记得最初在学习递归的时候,一个经典的案例就是斐波那契数列 f ( n ) = f ( n − 1 ) + f ( n − 2 ) , n ≥ 2 f(n) = f(n-1) + f(n-2), n\geq2 f(n)=f(n1)+f(n2),n2
我们发现,当递归调用的时候,其中某一个子树的值可能会被重复计算很多次,很费时间:
图片摘自网络,侵删
例如图中 f ( 2 ) f(2) f(2)的值就被重复计算了三次,那么我们能不能让它只计算一次呢?

动态规划(DP)思想

对于斐波那契数列的一种优化方法就是利用数组暂存中间值,这样就避免了重复计算
那么什么又是动态规划?

遵循动态规划算法是将问题递归地分解成更简单的重叠子问题,通过子问题的解决来解决原问题
动态规划目前已广泛应用于经济学、计算机视觉、语音识别、人工智能、计算机图形学和生物信息学

在斐波那契数列的例子中,多次计算的 f ( 2 ) f(2) f2的值,这样就是一个重叠的子问题,我们应避免重复计算带来的时间开销,于是引入了数组——保存每一次计算的值(这样一个过程称为记忆, 对于提高递归算法效率是很重要的技术)

3.优化

我们有了思路,下面的关注点就在于寻找该问题的一个递推关系式
我们先列出来一部分:(其中 f ( n ) f(n) f(n)表示自然数 n n n所具有上述三种性质的个数)

f ( 1 ) = 1 f(1) = 1 f(1)=1
f ( 2 ) = 2 f(2) = 2 f(2)=2 f ( 3 ) = 2 f(3) = 2 f(3)=2
f ( 4 ) = 4 f(4) = 4 f(4)=4 f ( 5 ) = 4 f(5) = 4 f(5)=4
f ( 6 ) = 6 f(6) = 6 f(6)=6 f ( 7 ) = 6 f(7) = 6 f(7)=6
f ( 8 ) = 10 f(8) = 10 f(8)=10 f ( 9 ) = 10 f(9) = 10 f(9)=10
我们可以看出:
  • n n n为奇数时( n > 1 n>1 n>1), f ( n ) = f ( n − 1 ) f(n) = f(n-1) f(n)=f(n1)

  • n n n为偶数时,分析却复杂一些,我们不妨画出其递归树结构:
    在这里插入图片描述
    f ( 4 ) f(4) f(4)为例,观察其结构,我们发现右框部分就是 f ( 4 / 2 ) f(4/2) f(4/2)的个数,那么左边是什么呢?
    仔细思考左框可以看出,左部分的个数相当于少了一个 f ( 4 / 2 ) f(4/2) f(4/2)子树的个数,这不正好等于 f ( 3 ) f(3) f(3)吗?

综上,我们可以得出递推式:

  1. f ( 1 ) = 1 f(1) = 1 f(1)=1
  2. n > 1 n>1 n>1
    • n n n为奇数, f ( n ) = f ( n − 1 ) f(n) = f(n-1) f(n)=f(n1)
    • n n n为偶数, f ( n ) = f ( n − 1 ) + f ( n / 2 ) f(n) = f(n-1) + f(n/2) f(n)=f(n1)+f(n/2)

代码

long fun(int n){
	long a[n+1];

	a[0] = 1;
	
	for(int i=1; i<n+1; i++){
		if(i%2 != 0)    //奇数
			a[i] = a[i-1];
		else
			a[i] = a[i-1] + a[i/2];
	}
	
	return a[n];
}

4.小结

学习算法的道路漫长且艰巨,唯有不断打磨自己不断思考,挖掘其背后的本质,才能取得进步

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值