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;

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

相关推荐
飞Link3 小时前
什么是 AirSim?一文读懂无人机仿真平台
python·算法·无人机
xiaoye-duck5 小时前
《C++ 工具库实战》gflags 命令行参数解析库详解
c++·第三方库
一去不复返的通信er5 小时前
贪心算法-合并区间(LeetCode 56)
算法·leetcode·贪心算法
狂奔的sherry6 小时前
C++ 宏生成访问函数的代价:访问权限、调试和类型检查
c++·宏·编译·预处理器·类型检查
Shaoshing6 小时前
LeetCode100 49. 字母异位词分组
java·算法
YFJ_mily6 小时前
第二届区块链技术与基础模型国际学术会议(BTFM2027)
算法·区块链·能源·智能系统·rdlink研发家·机器人自动化
汤姆宝库7 小时前
收官深拆:第一性原理、产业电网与未来推演
大数据·人工智能·算法
划破黑暗的第一缕曙光7 小时前
[C++]:5.STL:string(2)
java·c++·redis
moshuying9 小时前
块扩散 + GDN 改造:Clover 把「并行生成」塞进了 Agent 的长上下文里
前端·算法