题目描述

给出自然数 洛谷-P1028-数的计算_递归,要求按如下方式构造数列:

  1. 只有一个数字 洛谷-P1028-数的计算_递归 的数列是一个合法的数列。
  2. 在一个合法的数列的末尾加入一个自然数,但是这个自然数不能超过该数列最后一项的一半,可以得到一个新的合法数列。

请你求出,一共有多少个合法的数列。两个合法数列 洛谷-P1028-数的计算_记忆化搜索_03 不同当且仅当两数列长度不同或存在一个正整数 洛谷-P1028-数的计算_递归_04,使得 洛谷-P1028-数的计算_递归_05

输入格式

输入只有一行一个整数,表示 洛谷-P1028-数的计算_递归

输出格式

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

输入输出样例

输入 #1复制

6

输出 #1复制

6

说明/提示

样例 1 解释

满足条件的数列为:

  • 洛谷-P1028-数的计算_记忆化搜索_07
  • 洛谷-P1028-数的计算_递归_08
  • 洛谷-P1028-数的计算_递归_09
  • 洛谷-P1028-数的计算_记忆化搜索_10
  • 洛谷-P1028-数的计算_记忆化搜索_11
  • 洛谷-P1028-数的计算_递归_12

数据规模与约定

对于全部的测试点,保证 洛谷-P1028-数的计算_记忆化搜索_13


思路分析:

题目给定一个数n,要我们求满足题中所说队列的个数有多少个。假设n=8,首先第一个合法队列是它本身8,另外的合法队列需要满足不能超过8的一半也就是4,我们按照往队列一个一个加来算,接下来加进去的数只能是1、2、3、4,这又额外组成了4种合法队列;再往下加的数同样需要满足比刚刚我们加的那个数的一半小或者相等,加入刚刚加进队列的是4,则这一次只能加1、2。如果刚刚加进的是1,则这一次不能再加。

洛谷-P1028-数的计算_递归_14

画出树图之后,我们发现这是一个很明显的递归问题,对于n=8这个大问题,我们分解成4个小问题,这四个小问题之和就是大问题的解,然而要求出这四个小问题我们依然要继续划分成更多个小问题,直到分到x=1时,无法继续分解(相当于题中所说不做任何处理直接统计为一种合法队列)。

我们可以写出递归函数的代码如下:

static int sol(int x){
int ans = 1; // 它本身可以是一个合法队列,初始赋值为1
if(x == 1) return 1;
for(int i = 1; i <= x / 2; i++){
ans += sol(i);
}
return ans;
}

但是我们发现,题中给的n最大是1000,如果按照上面暴力递归肯定是会超时。因为我们每去求一个分支,不管前面分支是否对同样的值求过,都要走到尾重新求一边(比如:分支x=4走到底要求x=2一次和x=1两次,而后面分支321同样求了2或者1,这里所谓的求是指调用了sol函数)。我们要剔除过多的冗余操作,对于每一个小分支我们只需要求一次即可,把每次求出的值都放到一个数组中,即记忆化搜索。


最终代码如下:

import java.util.Arrays;
import java.util.Scanner;

public class 数的计算 {
static final int N = 1005;
static int n;
static int[] f = new int[N];
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
// 默认值为-1,如果求过值则改变
Arrays.fill(f, -1);
System.out.println(sol(n));
}
static int sol(int x){
int ans = 1;
// 如果前面求过x的值则不用再计算
if(f[x] != -1) return f[x];
// 求值
for(int i = 1; i <= x / 2; i++) ans += sol(i);
// 存入数组并输出
return f[x] = ans;
}
}