【P1028】数的计算 Noip2001(递归递推记忆化搜索前缀和优化)

题目

我们要求找出具有下列性质数的个数(包含输入的自然数n):
先输入一个自然数n(n≤1000),然后对此自然数按照如下方法进行处理:
在它的左边加上一个自然数,但该自然数不能超过原数的一半;
加上数后,继续按此规则进行处理,直到不能再加自然数为止.

输入格式
1个自然数n(n≤1000)

输出格式
1个整数,表示具有该性质数的个数。

输入输出样例
输入
6
输出
6
说明/提示
满足条件的数为

6,16,26,126,36,136
在这里插入图片描述
思路:

放在最前的最终版
本题有很多做法,可以递归、递推、加上各种优化
先把最终版的放前边方便看,之后五种方法适合不同程度小可爱们依次进阶食用
根据题意,我们分析下
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[i] 是从 F[1] + … + F[i/2] + 1 转移过来的(看不懂在方法3那里有图哦)
并且 F 数组显然是按顺序求的(也就是说前缀和是不带修改的)
所以用一个前缀和维护一下可以 O(1) 求每个 F
初值不用设,答案 F[n]
代码如下

#include<bits/stdc++.h>
using namespace std;
int n,ans[10086233],sum[10086233];
int main()
{
	scanf("%d",&n);
	for(int i = 1 ; i <= n ; i++ )
		sum[i] = sum[i-1] + ( ans[i] = sum[i>>1] + 1 ) ;
	printf("%d\n",ans[n]);
	return 0;
}

辣么我们从暴力到理智来一步步进阶思考

1·简单递归法
用递归,f(n)=1+f(1)+f(2)+…+f(div/2),当n较大时会超时,时间应该为指数级。(TLE妥妥的)

代码如下:

#include<bits/stdc++.h>
using namespace std;
int ans;
void dfs(int m)                           //统计m所扩展出的数据个数
{
    int i;
    ans++;                                  //每出现一个原数,累加器加1;
    for (i = 1; i <= m/2; i++)         //左边添加不超过原数一半的自然数,作为新原数
      dfs(i); 
}
int main()
{
    int n;
    cin >> n;
    dfs(n);
    cout << ans;
    return 0;
} 

2·记忆化搜索优化
优化采取记忆化搜索。实际上是对上边改进。设h[i]表示自然数i满足题意三个条件的数的个数。如果用递归求解,会重复来求一些子问题。例如在求h[4]时,需要再求h[1]和h[2]的值。现在我们用h数组记录在记忆求解过程中得出的所有子问题的解,当遇到重叠子问题时,直接使用前面记忆的结果。
AC木得问题

#include<bits/stdc++.h>
using namespace std;
int h[10086233];
void dfs(int m)
{
    int i;
    if (h[m] != -1) return;         //说明前面已经求得h[m]的值,直接引用即可,不需要再递归
    h[m] = 1;                           //将h[m]置为1,表示m本身为一种情况
    for (i = 1; i <= m/2; i++)
    {
        dfs(i);
        h[m] += h[i];
    }   
}
int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
      h[i] = -1;                         //h数组初始化为-1
    dfs(n);                              //由顶到下记忆化递归求解
    cout << h[n];   
    return 0;
}

3·递推法
要求比较好的数学推理,需要深刻理解题目
用递推,用h(n)表示自然数n所能扩展的数据个数,则h(1)=1, h(2)=2, h(3)=2, h(4)=4, h(5)=4, h(6)=6, h(7)=6, h(8)=10, h(9)=10.分析以上数据,可得递推公式:h(i)=1+h(1)+h(2)+…+h(i/2)。此算法的时间度为O(n*n)。
(当然这个也容易TLE啦)
设h[i]-i按照规则扩展出的自然数个数(1≤i≤n)。下表列出了h[i]值及其方案:
在这里插入图片描述
代码比较简单

#include<bits/stdc++.h>
using namespace std;
int h[10086233];
int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)   //按照递增顺序计算扩展出的自然数的个数
    {
        h[i] = 1;                        //扩展出的自然数包括i本身
        for (int j = 1; j <= i/2; j++)         
                                 //i左边分别加上1…自然数 按规则扩展出的自然数
          h[i] += h[j]; 
    }
    cout << h[n];
    return 0;
}

4·前缀和优化
优化方法:前缀和。也是对上边的改进,我们定义数组s,s(x)=h(1)+h(2)+…+h(x),h(x)=s(x)-s(x-1),(s[]数组是h累加前缀和)。此算法的时间复杂度可降到O(n)。

#include<bits/stdc++.h>
using namespace std;
int h[10086233],s[10086233];
int main()
{
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        h[i] = 1 + s[i/2];
        s[i] = s[i-1] + h[i]; 
    }
    cout << h[n];
    return 0;
}

5·数学奥义递推法
当然最强的还是运用数学知识深刻理解题意,
这里给的建议是可以想做数学数列一样,从1开始多写几种情况,然后观察推测规律。
还是用递推,只要作仔细分析,其实我们还可以得到以下的递推公式:
(1)当i为奇数时,h(i)=h(i-1);
(2)当i为偶数时, h(i)=h(i-1)+h(i/2).

#include<bits/stdc++.h>
using namespace std;
int h[10086233];
int main()
{
    int n;
    cin >> n;
    h[1] = 1;
    for (int i = 2; i <= n; i++)
    {
        h[i] = h[i-1];
        if (i % 2 == 0) h[i] = h[i-1] + h[i/2];
    }
    cout << h[n];
    return 0;
}

结合当年大佬以及课件讲解写的卑微题解,祝大家每道题都顺利AC

发布了75 篇原创文章 · 获赞 80 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_36693514/article/details/102517413
今日推荐