洛谷——P1028 数的计算

题目描述

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

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

  1. 不作任何处理;

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

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

输入格式

自然数nnn,且n1000n\leq1000n≤1000

输出格式

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

输入输出样例

输入

6

输出

6

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


1.首次尝试

解法一 递归

显然,最简单直接的做法就是递归(将一个大问题规约成更小规模的子问题,通过子问题的求解完成原问题)
假设:
当输入为4时,满足的其性质的数有——4本身,加上其规模1/2^1/_21/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(n1)+f(n2),n2f(n) = f(n-1) + f(n-2), n\geq2f(n)=f(n−1)+f(n−2),n≥2
我们发现,当递归调用的时候,其中某一个子树的值可能会被重复计算很多次,很费时间:
洛谷——P1028 数的计算
例如图中f(2)f(2)f(2)的值就被重复计算了三次,那么我们能不能让它只计算一次呢?

动态规划(DP)思想

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

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

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

3.优化

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

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

我们可以看出:

  • nnn为奇数时(n>1n>1n>1),f(n)=f(n1)f(n) = f(n-1)f(n)=f(n−1)

  • nnn为偶数时,分析却复杂一些,我们不妨画出其递归树结构:
    洛谷——P1028 数的计算
    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)=1f(1) = 1f(1)=1
  2. n>1n>1n>1时
    • nnn为奇数,f(n)=f(n1)f(n) = f(n-1)f(n)=f(n−1)
    • nnn为偶数,f(n)=f(n1)+f(n/2)f(n) = f(n-1) + f(n/2)f(n)=f(n−1)+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.小结

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

上一篇:互联网运作


下一篇:(C#) 精简 C# 入门(二)