数的计算

数的计算 洛谷

题目描述

给出自然数 \(n\),要求按如下方式构造数列:

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

请你求出,一共有多少个合法的数列。两个合法数列 \(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}\) 这些数有关系,即可得到状态转移方程

\[f_i=\sum_{j=0}^{\frac{i}{2}} f_j \]

(从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;
}

能用记忆化搜索是因为它本身是有重复调用部分的,只要有重复,就可以考虑用记忆化,不过并不是所有题目都可以,有一些题目并不能从动态规划变成记忆化搜索,或者比较麻烦,所以还是要学会动态规划(不过我目前还没遇到过这种题目)

顺便说一句,虽然记忆化搜素和动态规划的理论时间复杂度相等,但实际上记忆化搜素会慢一点点,这是因为记忆化搜索毕竟还是基于递归,自然会略微逊色,所以对于一些可能要卡常的题目,建议再把记忆化搜索变成动态规划

posted @ 2023-05-08 22:50  HEIMOFA  阅读(287)  评论(0)    收藏  举报