题目描述
我们要求找出具有下列性质数的个数(包含输入的自然数nn):
先输入一个自然数n(1<=n≤1000),然后对此自然数按照如下方法进行处理:
不作任何处理;
在它的左边加上一个自然数,但该自然数不能超过原数的一半;
加上数后,继续按此规则进行处理,直到不能再加自然数为止.
输入格式
1个自然数
输出格式
11个整数,表示具有该性质数的个数。
输入输出样例
输入 #1
6
输出 #1
6
说明/提示
满足条件的数为
6,16,26,126,36,136
传送门洛谷P1028
第一篇题解,先说一下思路
这个使用递归公式计算的
f(n) = ∑ i = 0 n 2 f ( i ) \sum_{i=0}^\frac{n}{2} f(i) ∑i=02nf(i)
因此这个题实际是一个求前n项和的过程
这里我使用的是与递归相反的一个正向从前往后的思路,也就是从递归的根节点逐步向上,避免重复(其实前n项和就是不断重复)
接下来我们对公式进行优化
2 n / 2 = ( 2 n + 1 ) / 2 ⇒ { f ( 2 n ) = ∑ i = 0 n f ( i ) F ( 2 n + 1 ) = ∑ i = 0 n F ( i ) 2n/2 = (2n+1)/2 \Rightarrow \begin{cases} f(2n) =\sum_{i=0}^{n} f(i) & \\ \\\\ F(2n+1) = \sum_{i=0}^{n} F(i) & \end{cases} 2n/2=(2n+1)/2⇒⎩⎪⎪⎪⎨⎪⎪⎪⎧f(2n)=∑i=0nf(i)F(2n+1)=∑i=0nF(i)
因此我们发现f(2n)==f(2n+1),并且前n项和连续,因此我们节省了一半的空间与时间
对于数组num, num[i/2] = f(n/2)(这里n是自然数n不要混淆)
由于之前奇数偶数的性质我们开始对问题进行分类
f ( n ) = { f ( 2 n ) = f ( n ) + f ( 2 n − 1 ) F ( 2 n + 1 ) = F ( 2 n ) f(n) = \begin{cases} f(2n) =\ f(n)+f(2n-1) & \\ \\\\ F(2n+1) = \ F(2n) & \end{cases} f(n)=⎩⎪⎪⎪⎨⎪⎪⎪⎧f(2n)= f(n)+f(2n−1)F(2n+1)= F(2n)
接下来证明n为偶数的公式
f ( 2 n ) f(2n) f(2n) - f ( 2 n − 1 ) f(2n-1) f(2n−1)
= ∑ i = 0 n f ( i ) \sum_{i=0}^{n} f(i) ∑i=0nf(i) - ∑ i = 0 n − 1 f ( i ) \sum_{i=0}^{n-1}f(i) ∑i=0n−1f(i)
= f ( n ) f(n) f(n)
f ( 2 n ) f(2n) f(2n) = f ( n ) f(n) f(n) - f ( 2 n − 1 ) f(2n-1) f(2n−1)
到了这里完整的思路就成型了
对于自然数n,我们只需要求出前n/2项和就可以完成计算
而因为 f ( 2 n ) f(2n) f(2n) = f ( 2 n + 1 ) f(2n+1) f(2n+1)的特性我们对空间进行压缩,同时也节约了一半的时间,近似达到了O(n/4)(速度在80ms近似相同)
值得注意的是因为空间的压缩前n项和公式发生了变化
F ( n ) F(n) F(n)
= ∑ i = 0 n 2 f ( i ) \sum_{i=0}^\frac{n}{2} f(i) ∑i=02nf(i)
= ∑ i = 0 k f ( i ) \sum_{i=0}^{k} f(i) ∑i=0kf(i)
= ∑ i = 0 k / 2 f ( i ) \sum_{i=0}^{k/2} f(i) ∑i=0k/2f(i)
f
(
n
)
=
{
1
,
n
=
=
1
2
∗
F
(
n
)
,
k
&
1
=
=
1
2
∗
F
(
n
)
−
f
(
k
/
2
)
,
k
&
1
=
=
0
f(n) = \begin{cases} 1&,n == 1\\ 2*F(n) & ,k\&1 == 1 \\ 2*F(n) - \ f(k/2) & ,k\&1 == 0\end{cases}
f(n)=⎩⎪⎨⎪⎧12∗F(n)2∗F(n)− f(k/2),n==1,k&1==1,k&1==0
(n为自然数,k = n/2)
解释一下第一个情况大于1时组合都包括本身和1所以累加的时候2* f ( 0 ) f(0) f(0)就代表这两个值,而1属于特例所以特殊处理
而第三种之前说过 f ( 2 n ) f(2n) f(2n)= f ( 2 n + 1 ) f(2n+1) f(2n+1)
所以2* f ( n ) f(n) f(n)时相对于k是偶数的,重复加了一次需要减去
f(8)
= ∑ i = 0 4 f ( i ) \sum_{i=0}^{4} f(i) ∑i=04f(i)
= ∑ i = 0 2 F ( i ) \sum_{i=0}^{2} F(i) ∑i=02F(i)- f ( 2 ) f(2) f(2)
sum[8]
= sum[4]+sum[3]+sum[2]+sum[1]+1
= 2*(sum[2]+sum[1]+sum[0])-sum[2]
第一次写题解废话多,逻辑可能不严谨,希望大佬们能指正
#include <iostream>
#include <cstring>
#include <map>
int num[1000];
inline int read(){
char c = getchar();
int num = 0;
while(c<'0'||c>'9'){
c = getchar();
}
while(c>='0'&&c<='9'){\
num = (num<<1)+(num<<3)+(c-'0');
c = getchar();
}
return (num)>>1;
//f(2n)==f(2n+1);
}
int main()
{
num[0] = 1;//此处n代表原自然数的一半
int sum =1,n = read(),len = n>>1;
bool f = n&1;
int i;
//这里的作用是f(i)的值应为n/2的前n项和,而这里因为f(2n) = f(2n+1),因此将空间压缩,同时也降低了一半的时间消耗
for(i=1;i<=len;i++){
num[i] = num[i-1]+num[i>>1];
sum+=num[i];
}
//对于的值都包含本身和一的前缀,而对于1只包含本身因此要减一
if(n){
if(f&1){
std::cout<<(sum<<1);
}else{
std::cout<<(sum<<1)-num[i-1];
}}
else
std::cout<<1;
return 0;
}