上链接:https://www.luogu.com.cn/problem/P1028
上题干:
题目描述
给出正整数 n,要求按如下方式构造数列:
- 只有一个数字 n 的数列是一个合法的数列。
- 在一个合法的数列的末尾加入一个正整数,但是这个正整数不能超过该数列最后一项的一半,可以得到一个新的合法数列。
请你求出,一共有多少个合法的数列。两个合法数列 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);
}