P1028 [NOIP 2001 普及组] 数的计算

题目链接:P1028 NOIP 2001 普及组 数的计算 - 洛谷

我的解法:

递归 + 双指针

cpp 复制代码
#include<vector>
#include<iostream>
#include<algorithm>
using namespace std;
vector<long long> arr, m;
int find(vector<long long>& arr, int le, int rt)
{
    int l = le, r = rt, dest = 0;
    int ans = 0;
    if (r == 0)
    {
        return 0;
    }
    if (m[r] != -1) return m[r];
    while (l < r)
    {
        if (arr[l] <= (arr[r] / 2))
        {
            ans += 1;
            dest = l;
        }
        l++;
    }
    while (dest) ans += find(arr, 0, dest--);
    return m[r] = ans;
}
int main()
{
    int n, coun = 1;
    cin >> n;
    arr.push_back(n);
    n /= 2;
    while (n > 0)
    {
        arr.push_back(n);
        n--;
    }
    sort(arr.begin(), arr.end());
    int l = 0, r = arr.size() - 1;
    m.assign(r + 1, -1);
    coun += find(arr, l, r);
    cout << coun;
    return 0;
}

思路:

先将所有可能构成"合法数列"的值导入数组arr:

首先是n自己,其次发现 ≤ n / 2的数都可以(因为都 ≤ n 的一半)

然后从小到大排序

下面以n = 100举例

之后思考递归怎么写:

首先要构成"合法数列",数列中有一个元素的情况就一种:

也就是n自己

所以coun初始值为1

之后递归函数f为求出构成"合法数列"时,元素 ≥ 1的情况

在f函数里,先解决两个元素的情况:

cpp 复制代码
int find(vector<long long>& arr, int le, int rt)
{
    int l = le, r = rt, dest = 0;
    int ans = 0;
    if (r == 0)
    {
        return 0;
    }
    while (l < r)
    {
        if (arr[l] <= (arr[r] / 2))
        {
            ans += 1;
            dest = l;
        }
        l++;
    }

基线条件一会再说

while循环就实现了找出所有二元数组:

左指针不断向前移动,满足小于当前右指针的数的一半这个条件时,将数组个数ans++(代表找到一个二元数组)

并且记录一下最后出现的有效左指针,作为下一步循环的右指针

(因为要组成三元或多元数组,必须还要小于上一个数的一半)

求多元的数组就可以把上一个元数"视而不见",继续进行当前的寻找二元数组,就可以实现查找多元数组的效果

但是需要考虑另外一个种情况:

如果我们下一步开始递归只写

cpp 复制代码
ans += find(arr, 0, dest);

就只能做到跳跃性查找:

100 到 50 到 25 到 ...

但是100 后可以取49 48

而50后又可以取24 23

所以要这样写:

cpp 复制代码
    while (dest) ans += find(arr, 0, dest--);

以其实100举例:

这就实现了 50 - 1所有可能数的取值

之后再返回ans就好了。

但这样为止会TLE

所以又加了记忆数组,避免重复的计算


最佳解法:

递进 + 前缀和

cpp 复制代码
#include<iostream>
#include<vector>
using namespace std;
int main()
{
    int n;
    cin >> n;
    vector<long long> f(n + 1, 0), s(n + 1, 0);
    for (int i = 1; i <= n; i++)
    {
        f[i] = 1 + s[i / 2];
        s[i] = f[i] + s[i - 1];
    }
    cout << f[n] << endl;
}

这个解法是从底层算起,不断往上算出:

f(n) 为以n结尾的合法数列个数

算f(n) 满足:

最后1为本身自己构成的

cpp 复制代码
        f[i] = 1 + s[i / 2];
        s[i] = f[i] + s[i - 1];

s数列是为了避免重复计算,代表的意义是公式中的

当i为1时

f(1) = 1 + s0,s0 = 0;

所以f(1) = 1;

并更新当前i的前边几项和

s1 = 1;

当i为2时:

f(2) = 1 + s1,s1 = 1

f(2) = 2;

s2 = 3;

当i为3时:

f(3) = 1 + s1

s1 = 1

f(3) = 2;

s3 = 5;

............

相关推荐
Keven_111 小时前
BUG:UVA116
算法·bug·图论
Henry-SAP2 小时前
Beam大模型开源引爆AI热潮
算法
垆边人似月.2 小时前
日志时间窗口内的最大并发请求数(100分 / 滑动窗口 + 排序)
数据结构·c++·算法
Nil2082 小时前
leetcode 70爬楼梯
算法·leetcode·动态规划
垆边人似月.2 小时前
最长递增子序列的个数(200分 / 动态规划)
算法·动态规划
Chen_harmony2 小时前
一、C++入门基础
开发语言·c++
水饺编程2 小时前
第1章,[Win32 章节]:Windows 的方方面面
c语言·c++·windows·visual studio
镜子AI2 小时前
教培机构多模态内容跨模态检索系统:基于CLIP的统一表征与图文混合召回算法
人工智能·python·算法·机器学习
小宋10213 小时前
合成评测集怎么造才不“自嗨”:自动出题、去重、泄漏检测与人工抽检
人工智能·算法·机器学习