思念变成海,在窗外进不来——洛谷P1028 数的计算 记忆化递归

上链接:https://www.luogu.com.cn/problem/P1028

上题干:

题目描述

给出正整数 n,要求按如下方式构造数列:

  1. 只有一个数字 n 的数列是一个合法的数列。
  2. 在一个合法的数列的末尾加入一个正整数,但是这个正整数不能超过该数列最后一项的一半,可以得到一个新的合法数列。

请你求出,一共有多少个合法的数列。两个合法数列 a,b 不同当且仅当两数列长度不同或存在一个正整数∣i≤∣a∣,使得 ai​ != bi​。

输入格

输入只有一行一个整数,表示 n。

输出格式

输出一行一个整数,表示合法的数列个数。

输入输出样例

输入 #1复制

6

输出 #1复制

6

说明/提示

样例 1 解释

满足条件的数列为:

  • 6
  • 6,1
  • 6,2
  • 6,3
  • 6,2,1
  • 6,3,1

数据规模与约定

对于全部的测试点,保证 1≤n≤10^3。

递推做法:

假设n=1,f(1)表示当n=1时候,这样的序列的所有可能。

我们知道,在这样的序列后面每次可以加一个不超过前面的数的一半的数字,或者不加。

所以1后面不超过前面一半的数字就是0,也就是不加任何数字。(f(0)代表不加任何数字

即:f(1)=f(0)

假设n=2,2后面加不超过2一半的数字,即1,或不加。

即:f(2)=f(1)+f(0) 

假设n=4,则有 f(4)=f(2)+f(1)+f(0);

那么递推通式就是  f(n)=f(n/2)+f(n/2-1)+.....+f(2)+f(1)+f(0)

所以就有:

f[100000]={0};
f[0]=1;

for(int i=1;i<=n;i++)
   for(int j=0;j<=i/2;j++)
       f[i]+=f[j];

递归做法:

假设n=6

我们的最终目的是求f(6), f(6)后面第一层数又可以跟 0,1,2,3 (0就是不跟的意思)

而 0,1,2,3 后面第一层,又可以跟其他数字。 (此时6后面一共两层数字了)

当某一层数字无法继续递归下去的时候,就要返回值。 

所以我们画出的递归图应该是这样的:

 所以代码实现应该是这样的:

int recursion(int x)
{
   int ans=0;
   if(x==0)return 1;

   for(int i=0;i<=x/2;i++)
   {
       ans+= recursion(i);
   }
     
    return ans;
}

但是,单纯的递归,做这道题却无法AC,因为这样的效率太慢了,

比如说 recursion(2) 的时候 会递归一次 i=0,但是i=0的时候在循环的外层直接return 1了,不会进入循环导致继续递归(因为循环外部有if判断语句)。

但是recursion (2) 的时候 , 会递归2次,第一次是i=0的时候递归,它不会进入循环。

但是第二次,i=1的时候会进入循环:for(int i=0;i<=1/2;i++) 。

然而,i=1的值是不变的,这样每次都递归的方法太慢了。

如果我们能把每次递归过的值存入一个数组中,再每次递归之前,判断一下这个值是否被求出。

如果被求出了,我们直接在外层返回这个值就好了,不需要进入循环再次递归求值。

这样就能大大地加快递归效率了。

所以我们有了如下的代码:

int a[10000];
int dfs(int x)
{
	int ans = 0;
	if (a[x])return a[x];
	for (int i = 0; i <= x / 2; i++)
	{
		ans += dfs(i);
		if (i == x / 2)
		{
			a[x] = ans;
			return ans;
		}
	}
}
int main()
{
	int n;    cin >> n;
	a[0] = 1; a[1] = 1;
	cout<<dfs(n);
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

louisdlee.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值