数的计算
题目描述
给出自然数 \(n\),要求按如下方式构造数列:
- 只有一个数字 \(n\) 的数列是一个合法的数列。
- 在一个合法的数列的末尾加入一个自然数,但是这个自然数不能超过该数列最后一项的一半,可以得到一个新的合法数列。
请你求出,一共有多少个合法的数列。两个合法数列 \(a, b\) 不同当且仅当两数列长度不同或存在一个正整数 \(i \leq |a|\),使得 \(a_i \neq b_i\)。
输入格式
输入只有一行一个整数,表示 \(n\)。
输出格式
输出一行一个整数,表示合法的数列个数。
样例输入
6
样例输出
6
提示
对于全部的测试点,保证 \(1 \leq n \leq 10^3\)。
首先可以分析一下题目,就拿样例来说,有两个数列 \(6\ 3\ 1\) 和 \(6\ 3\) ,为什么会出现这两个数列呢?实际上是对 \(3\) 进行构造,构造出 \(3\ 1\) 和 \(3\) 两个数列,也就是说原本的两个数列其实与 \(6\) 并没有关系,只与 \(3\) 有关系,即使是 \(7\ 3\ 1\) 和 \(7\ 3\) 也只与3有关系,很明显这是无后效性
拥有无后效性的题目最好的解决办法当然是动态规划,根据题目可知,一个数 \(n\) 与 \(1,2,3,...,\frac {n} {2}\) 这些数有关系,即可得到状态转移方程
(从0开始是因为可以直接不取,自然就是0)
动态规划还需要边界条件,这个很简单 \(f[0]=1\)
\(Code:\)
#include<bits/stdc++.h>
using namespace std;
int n;
const int N=1005;
int f[N];
int main()
{
cin>>n;
f[0]=1;//边界条件
for(int i=1;i<=n;i++){
for(int j=0;j<=i/2;j++) f[i]+=f[j];//求和累加
}
cout<<f[n];
return 0;
}
动态规划自然也可以用记忆化搜索来做,我们首先可以先打一遍递归,再把递归变成记忆化搜索即可
\(Code:\)
#include<bits/stdc++.h>
using namespace std;
int n;
int dfs(int x){//这里采取的是dfs爆搜,应该也叫递归
if(x==0) return 1;
int ans=0;
for(int i=0;i<=x/2;i++){
ans+=dfs(i);
}
return ans;
}
int main()
{
cin>>n;
cout<<dfs(n);
return 0;
}
如果暴力递归会的话,改成记忆化搜素自然毫不费力啦
\(Code:\)
#include<bits/stdc++.h>
using namespace std;
int n;
const int N=1005;
int opt[N];
int f(int x){
if(opt[x]) return opt[x];
for(int i=0;i<=x/2;i++) opt[x]+=f(i);
return opt[x];
}
int main()
{
cin>>n;
opt[0]=1;
cout<<f(n);
return 0;
}
能用记忆化搜索是因为它本身是有重复调用部分的,只要有重复,就可以考虑用记忆化,不过并不是所有题目都可以,有一些题目并不能从动态规划变成记忆化搜索,或者比较麻烦,所以还是要学会动态规划(不过我目前还没遇到过这种题目)
顺便说一句,虽然记忆化搜素和动态规划的理论时间复杂度相等,但实际上记忆化搜素会慢一点点,这是因为记忆化搜索毕竟还是基于递归,自然会略微逊色,所以对于一些可能要卡常的题目,建议再把记忆化搜索变成动态规划