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≤�≤1031≤n≤103。
感想:
个人认为这个题目是一个非常好的题目,用于入门dfs,剪枝算法。
此题,我会给出多种解法。如下:
方法一:暴力
暴力递归搜索,这样复杂度太高,果然TLE了(doge).在自己的VS上跑的时候当数据为1000时也跑了很久很久。。。。。。
int dfs(int n)
{
int result = 1;//包括自身
for (int i = 1; i <= n / 2; i++)
{
result += dfs(i);
}
return result;
}
int main()
{
int n; cin >> n;
cout << dfs(n) << endl;
}
方法二:记忆化,剪枝
对于暴力搜索的优化,因为上面的暴力搜索有太多的重复计算,所以我们加入记忆剪枝算法(构造记忆数组,若先前获得了结果则直接利用结果无需重新计算)。
int remember[1005] = { 0 };
int dfs(int n)
{
if (remember[n])return remember[n];//若先前计算过了,则直接返回结果
int result = 1;//包括自身
for (int i = 1; i <= n / 2; i++)
{
result += dfs(i);
}
remember[n] = result;//在返回result前先记忆结果,将结果保存下来
return result;
}
int main()
{
int n; cin >> n;
cout << dfs(n) << endl;
}
方法三:递推公式
在样例中有:
- 6
- 6,1
- 6,2
- 6,3
- 6,2,1
- 6,3,1
对此进行分析,设f[n] ,数为n时数列的个数,则有
f[1]=1
f[2]=2=f[1]+1
f[3]=2=f[1]+1
f[4]=4=f[1]+f[2]+1
f[5]=4=f[1]+f[2]+1
不难发现 f(n) = f(1)+f(2)+ ...... +f(n/2) + 1;
得到状态方程(先求小,再求大)
其实可以这样考虑 对于f(i),只需要在该数列一号位插入一个x,x>=2*i.即可满足数列条件,对于任意的小于i的也是如此。
所以对于f(n) 是在从 f(1) ~ f(n/2) 前插入了一个n,故f (1) ~ f(n/2) 都对f(n)有贡献,而加一则是加上本身。
代码如下:
int f[1005] = { 0 };
int main()
{
int n; cin >> n;
for (int i = 1; i <= n; i++)f[i] = 1;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= i / 2; j++)
f[i] += f[j];
}
cout << f[n] << endl;
return 0;
}
递归优化与循环优化
有状态方程:f(n) = f(1)+f(2)+ ...... +f(n/2) + 1;
得 f(n-1) = f(1)+f(2)+ ...... +f( (n-1) / 2 ) + 1;
有很多求和有重复,故还可优化:令 f(n) = sum(n/2) + 1; 而sum(n) = f(1) + f(2) + ... + f(n)
得 f (n) = sum(n/2) + 1; 且 sum(i) = sum(i-1) + f(i);
可得以下代码:
//递推优化
int f[1005], sum[505];
//f为记忆结果的数组
//sum为先前求和数组
int F(int i); int Sum(int i);
int main()
{
int n; cin >> n;
f[1] = sum[1] = 1;
cout << F(n) << endl;
}
int F(int i)
{
if (f[i]) return f[i];//剪枝
return f[i] = Sum(i / 2) + 1;//利用赋值记忆
}
int Sum(int i)
{
if (sum[i]) return sum[i];//剪枝
return sum[i] = Sum(i - 1) + F(i);
}
***循环优化***
//迭代传播(循环)
int f[1005], sum[1005];
int main()
{
int n; cin >> n;
f[1] = 1;
for (int i = 2; i <= n; i++){
sum[i / 2] = sum[i / 2 - 1] + f[i / 2];
f[i] = sum[i / 2] + 1;
}
cout << f[n] << endl;
}
//迭代传播(循环)
int f[1005], sum[1005];
int main()
{
int n; cin >> n;
f[1] = sum[1] = 1;
for (int i = 2; i <= n; i++){
f[i] = sum[i / 2] + 1;
sum[i / 2 + 1] = sum[i / 2] + f[i / 2 + 1];
}
cout << f[n] << endl;
}