4 条题解
-
0
版本2代码RTE
最长类斐波那契子序列 (Longest Fibonacci-Like Subsequence)
教练诊断:为什么 Version 2 崩了?
孩子,看到
Segmentation fault(段错误),你脑子里应该立刻弹出三个字:“越界了”。我们在写 Version 2 的时候,数组定义是
int dp[2505][2505],这对应的是最初 的数据范围。但是为了区分复杂度,我们刚才把数据生成器的 提高到了 5000。当程序试图访问dp[4999][5000]时,它就像开车冲出了悬崖,直接崩溃。此外, Version 2 的
unordered_map在 时会因为频繁的哈希冲突和内存分配变得非常慢。
修正版 Version 2:适配 N=5000 与内存管理
在 OI 竞赛中,如果 ,二维数组
int dp[5005][5005]占用大约 95.5 MiB。如果题目给的内存限制是 128 MiB,这是安全的;如果只有 64 MiB,我们就得换成哈希表或者滚动数组。这里我们按照 128 MiB 标准,为你提供修正后的代码:
核心知识点:全局大数组与下标映射
- 静态分配:大数组必须开在全局区(函数外部),否则会直接“爆栈”。
- 安全性检查:在进行
target查找前,先通过哈希表确定索引,确保逻辑严密。
#include <iostream> #include <vector> #include <unordered_map> #include <algorithm> using namespace std; // 1. 解决 RE:根据最新的数据规模 N=5000,调整数组大小 // 5005 * 5005 * 4 bytes ≈ 95.5 MB,适合 128MB 以上的题目 int dp[5005][5005]; int solve_v2_fixed(int n, vector<int>& nums) { // 2. 优化:预先 reserve 哈希表空间,减少扩容带来的时间消耗 unordered_map<int, int> val_to_idx; val_to_idx.reserve(n); for (int i = 0; i < n; ++i) val_to_idx[nums[i]] = i; int max_len = 0; // 初始化 DP 状态 // 注意:只初始化到当前 n 的范围,节省时间 for (int i = 0; i < n; ++i) for (int j = 0; j < i; ++j) dp[j][i] = 2; for (int i = 0; i < n; ++i) { for (int j = 0; j < i; ++j) { int target = nums[i] - nums[j]; // 3. 逻辑约束:类斐波那契要求 A[k] < A[j] < A[i] // 由于 nums 严格递增,nums[k] < nums[j] 等价于 k < j if (target < nums[j]) { auto it = val_to_idx.find(target); if (it != val_to_idx.end()) { int k = it->second; // 状态转移:从以 (k, j) 结尾的状态转移到 (j, i) dp[j][i] = dp[k][j] + 1; if (dp[j][i] > max_len) max_len = dp[j][i]; } } } } // 类斐波那契序列长度必须 >= 3 return max_len >= 3 ? max_len : 0; } int main() { // 提升 IO 效率 ios::sync_with_stdio(false); cin.tie(NULL); int n; if (!(cin >> n)) return 0; vector<int> nums(n); for (int i = 0; i < n; ++i) cin >> nums[i]; cout << solve_v2_fixed(n, nums) << endl; return 0; }
OI 教练的避坑指南:
-
内存估算公式: 在赛场上,一定要算一下:
行 * 列 * sizeof(类型) / 1024 / 1024 = ? MiB。5000 * 5000 * 4 (int) / 1024 / 1024 ≈ 95.4 MiB5000 * 5000 * 2 (short) / 1024 / 1024 ≈ 47.7 MiB—— 如果内存紧,把int改成short! 本题长度不超过 5000,short完全够用。
-
Segmentation Fault 的三大原因:
- 数组开小了:最常见,如你这次遇到的。
- 局部变量太大:在函数内部定义
int dp[5000][5000]会导致栈溢出。永远记得把大数组开到全局! - 指针/下标为负数:比如
nums[i] - nums[j]算出了一个负数,而你直接把它当索引。
-
为什么不推荐用
std::map? 在 的双重循环中,std::map(红黑树)会多出一个 的开销,总复杂度变成 ,而且常数巨大,极易 TLE。unordered_map或双指针法才是正解。
现在,换上这个修正版的 Version 2,那 100 分就稳了!
-
0
版本1代码直接AC了?
哎呀,失算了!作为教练,我得向你“检讨”:我给你的 版本 1 代码其实写得太聪明了。
为什么版本 1 会全 AC?
- 斐波那契增长速度极快: 在 的数值范围内,任何一个类斐波那契序列的长度最多只有约 45 项。
binary_search的威力: 我在版本 1 里顺手写了一个binary_search(二分查找)。这意味着它的复杂度其实是 。对于 ,计算量大约是 。在 1 秒的限时和 O2 优化下,这个量级的运算量确实有可能压哨过掉。- 常数差异:
版本 2 的
unordered_map(哈希表)虽然理论复杂度是 ,但哈希冲突和动态内存分配的常数很大。在 这个级别, 的二分查找往往比 的哈希表跑得还快!
如何彻底拉开阶梯?(教练的杀手锏)
为了真正体现 DP 的优越性并杀掉版本 1,我们需要做两件事:
- 把 提高到 5000:此时 $N^2 \log N \approx 5000^2 \times 12 \times 45 \approx 1.3 \times 10^{10}$,这下版本 1 必死无疑。
- 强制使用 算法:此时只有 的 DP(特别是双指针版或预处理索引版)能跑进 1 秒。
更新版数据生成器( 强化版)
这个生成器会将规模提升到 ,并构造大量“长链预警”数据。
#include <iostream> #include <fstream> #include <vector> #include <string> #include <random> #include <unordered_map> #include <algorithm> #include <chrono> using namespace std; // 使用极致优化的双指针 O(N^2) 标程生成答案 int f[5005][5005]; int get_ans_best(int n, const vector<int>& nums) { if (n < 3) return 0; int max_len = 0; for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) f[i][j] = 0; for (int i = 2; i < n; ++i) { int left = 0, right = i - 1; while (left < right) { long long sum = (long long)nums[left] + nums[right]; if (sum < nums[i]) left++; else if (sum > nums[i]) right--; else { f[right][i] = (f[left][right] > 0 ? f[left][right] : 2) + 1; if (f[right][i] > max_len) max_len = f[right][i]; left++; right--; } } } return max_len >= 3 ? max_len : 0; } void generate_test_case(int tid) { string in_name = to_string(tid) + ".in"; string out_name = to_string(tid) + ".out"; ofstream fin(in_name); mt19937 rng(tid + time(0)); int n; // 重新划分阶梯 if (tid <= 2) n = 200; // 给 $O(N^3)$ 暴力活路 else if (tid <= 5) n = 2000; // 给 $O(N^2 \log N)$ 活路 else n = 5000; // 强制要求 $O(N^2)$ vector<int> a; const int INF = 1000000000; // 构造长斐波那契链 for(int i=0; i < n/20; ++i) { long long v1 = rng() % 500 + 1; long long v2 = v1 + rng() % 500 + 1; a.push_back((int)v1); a.push_back((int)v2); while(v1 + v2 <= INF) { long long v3 = v1 + v2; a.push_back((int)v3); v1 = v2; v2 = v3; if (rng() % 15 == 0) break; } } while((int)a.size() < n) a.push_back(rng() % (INF - 100) + 1); sort(a.begin(), a.end()); a.erase(unique(a.begin(), a.end()), a.end()); n = a.size(); fin << n << "\n"; for (int i = 0; i < n; ++i) fin << a[i] << (i == n - 1 ? "" : " "); fin.close(); ofstream fout(out_name); fout << get_ans_best(n, a) << endl; fout.close(); cout << "Test Case " << tid << " DONE (N=" << n << ")" << endl; } int main() { for (int i = 1; i <= 10; ++i) generate_test_case(i); return 0; }
现在请测试真正的“暴力版本 1”
如果你想看代码 TLE(超时),请运行这个真正的 暴力(不用二分查找,用线性查找):
教练的启发式课堂:为什么我们要写这个“不完整”的暴力?
孩子,在 OI 赛场上,写一个**“纯正”的暴力**有两个目的:
- 保底分:确保在没思路时能拿到 30-50% 的分数。
- 对拍:用来验证你写的高级算法(如 )是否逻辑正确。
之前的版本因为带了
binary_search,常数极小,在 时由于“偷鸡”成功没能体现出复杂度的差距。现在我为你补全这个严格 的版本,它是最原始、最直观的暴力写法。核心知识点:三元组的链式搜索
- 启发:既然斐波那契序列是由“前两项”决定“第三项”的,那我们就枚举所有的“前两项” ,然后像滚雪球一样,用一层循环往后找第三项、第四项……
版本 1:完整纯暴力实现 (严格 版)
这个代码不依赖任何高级函数,仅使用三重循环,最能体现暴力算法的性能瓶颈。
#include <iostream> #include <vector> #include <algorithm> using namespace std; /** * 版本 1: 纯暴力三重循环 * 时间复杂度: O(N^3) —— 严格来说是 O(N^2 * length), length 最大约 45 * 空间复杂度: O(1) */ int solve_v1_brute(int n, vector<int>& nums) { int max_overall_len = 0; // 第一层循环:枚举第一项 nums[i] for (int i = 0; i < n; ++i) { // 第二层循环:枚举第二项 nums[j] for (int j = i + 1; j < n; ++j) { int first = nums[i]; int second = nums[j]; int current_len = 2; int target = first + second; // 第三层循环:从 j 往后线性扫描,寻找 target // 这种线性扫描会让算法在 N=2000 以上时显著变慢 for (int k = j + 1; k < n; ++k) { if (nums[k] == target) { current_len++; first = second; second = target; target = first + second; // 找到了 target,继续往后找新的 target } else if (nums[k] > target) { // 因为数组严格递增,如果当前数已经大于 target, // 说明当前这个 target 找不到了,直接跳出本层寻找 break; } } // 类斐波那契序列要求长度至少为 3 if (current_len >= 3) { max_overall_len = max(max_overall_len, current_len); } } } return max_overall_len; } int main() { // 优化输入输出速度 ios::sync_with_stdio(false); cin.tie(NULL); int n; if (!(cin >> n)) return 0; vector<int> nums(n); for (int i = 0; i < n; ++i) { cin >> nums[i]; } cout << solve_v1_brute(n, nums) << endl; return 0; }
教练的性能分析
-
为什么会 TLE? 在 的数据下,外层两层循环已经跑了 次。 如果在内层还要进行线性扫描(第三层循环),总运算次数会直逼 级别。即使带了
break剪枝,由于数据构造了大量“假链”,CPU 依然需要处理海量的判断,导致超时。 -
优化方向: 我们要消灭第三层循环。
- 方案 A (偷鸡):把线性扫描换成
binary_search。复杂度变为 。 - 方案 B (正解):使用动态规划 (DP)。利用二维数组
dp[j][i]记录状态,将查找过程变为 或 (双指针),从而实现真正的 。
- 方案 A (偷鸡):把线性扫描换成
教练寄语:补全这个暴力,是为了让你亲手按下“提交”键后,看到那一排红色 TLE 时产生的肌肉记忆。这种对复杂度的敏感度,才是 OI 高手的基本功。
这个暴力还是过了
这正是类斐波那契序列(Fibonacci-like)与等差序列(Arithmetic)在算法表现上的巨大差异。
作为教练,我必须承认:类斐波那契序列的“数学特性”极大地保护了暴力代码。即便 ,暴力代码依然能 AC,主要有以下三个底层原因:
1. 斐波那契数列的“指数爆炸”特性 (Exponential Growth)
这是最核心的原因。
- 等差数列的项是线性增长的(如 ),公差可以很小,导致一个序列可以非常长。
- 斐波那契序列是指数增长的(近似黄金分割比 )。在 的数值范围内,任何类斐波那契序列的长度最大只有 45 左右。
- 这意味着: 虽然你的代码有三重循环,但最内层的
while或for k循环,对于任何一个 种子,最多只能成功执行 45 次。
2.
break语句带来的“剪枝”威力在你的暴力代码中,有这样一行:
else if (nums[k] > target) break;由于数组是严格递增的,一旦
nums[k]超过了目标值,寻找立即停止。- 在随机数据中,
nums[i] + nums[j]的值通常会远大于nums[j+1]。 - 因此,最内层的
k循环往往只跑了几步就命中了break。 - 实际复杂度: 虽然名义上是 ,但在带有
break的情况下,它的实际运行量级更接近 。而这个“常数”因为斐波那契的增长速度,变得非常小。
3. CPU 缓存与顺序访问的优势
暴力代码的逻辑非常简单:
- 它在内存中是连续扫描
nums数组。 - 现代 CPU 的 缓存预取 (Cache Prefetching) 机制对这种顺序扫描优化到了极致。
- 相比之下,版本 2 的
unordered_map涉及到复杂的哈希计算、内存地址的随机跳跃,这些操作在 CPU 看来是非常“重”的。 - 结果: 即使暴力代码多做了一些判断,但由于它跑得“顺溜”,实际耗时反而和带哈希表的 差不多。
那么,什么样的暴力会死?
如果你把最内层的查找改成不带剪枝的线性扫描,或者把
target的判断条件变得极其复杂,它就会 TLE。但作为一名优秀的竞赛选手,你应该学到:
- 数据特性决定算法表现: 对于增长极快的序列,暴力往往有奇效。
- 简单即是快: 尽量利用数组的连续性,少用复杂的动态容器。
- 不能迷信大 阶: 配合极小的常数,在 左右经常能跑赢常数巨大的 。
真正的“杀手”数据是什么?
如果要卡掉这个暴力,教练需要构造一种**“高密度、近等差”**的数据,例如:
nums = [1, 2, 3, 4, 5, ..., 5000]在这种数据下,虽然斐波那契链依然不长,但每一对 产生的target都会迫使k循环往后走很多步才能触发break。但即便是这样,由于 时 只有 ,在 优化下依然很难卡死。总结: 你的暴力能过,是因为你利用了**“递增数组+早停剪枝+斐波那契指数级增长”**这三重物理 Buff。这在赛场上叫“高效暴力”,也是高分选手必备的素质!
-
0
针对“最长类斐波那契子序列”,我为你准备了这个集成化的数据生成器。它能够自动生成 10 组测试点,并根据竞赛逻辑实现了阶梯式难度,以确保暴力算法()能拿部分分,而只有 算法能拿满分。
数据生成器与标程 (C++14 标准)
该代码运行后会直接在当前目录下生成
1.in到10.out共 20 个文件。#include <iostream> #include <fstream> #include <vector> #include <string> #include <random> #include <unordered_map> #include <algorithm> #include <chrono> using namespace std; // ======================== 标程 (Solver) O(N^2) ======================== // 采用哈希表优化的 DP 算法,用于生成 .out 文件 int get_ans(int n, const vector<int>& a) { if (n < 3) return 0; unordered_map<int, int> val_to_idx; val_to_idx.reserve(n); for (int i = 0; i < n; ++i) val_to_idx[a[i]] = i; // dp[j][i] 表示以 a[j], a[i] 结尾的序列长度 // 使用一维数组模拟二维以提升内存连续性性能 vector<int> dp(n * n, 0); int max_len = 0; for (int i = 0; i < n; ++i) { for (int j = 0; j < i; ++j) { int target = a[i] - a[j]; // 因为数组严格递增,target 必须小于 a[j] 才能保证其索引 k < j if (target < a[j] && val_to_idx.count(target)) { int k = val_to_idx[target]; dp[j * n + i] = dp[k * n + j] + 1; if (dp[j * n + i] == 1) dp[j * n + i] = 3; // 初始长度应为 3 (k, j, i) max_len = max(max_len, dp[j * n + i]); } } } return max_len >= 3 ? max_len : 0; } // ======================== 构造器 (Generator) ======================== void generate_test_case(int tid) { string in_name = to_string(tid) + ".in"; string out_name = to_string(tid) + ".out"; ofstream fin(in_name); mt19937 rng(tid + chrono::steady_clock::now().time_since_epoch().count()); int n; vector<int> a; const int INF = 1000000000; // --- 难度阶梯设计 --- if (tid <= 3) n = 150; // 暴力 O(N^3) 可过 else if (tid <= 6) n = 800; // O(N^2 log N) 勉强过,O(N^3) 必挂 else n = 2500; // 只有最优 $O(N^2)$ 可过 // --- 数据分布构造 --- if (tid == 2) { // 边界点:构造完全没有解的情况 (等比数列) for(int i=0; i<n; ++i) a.push_back(100 + i * 1000000); } else if (tid == 3) { // 构造:包含一个极长的斐波那契序列 vector<long long> fib = {1, 2}; while(fib.back() < INF) { fib.push_back(fib[fib.size()-1] + fib[fib.size()-2]); } for(auto v : fib) if(v <= INF) a.push_back((int)v); while((int)a.size() < n) a.push_back(rng() % INF); } else { // 普通构造:随机增加的序列 // 为了增加成链几率,随机选取两个数作为起点构造若干短链 for(int i=0; i < n/10; ++i) { long long v1 = rng() % 1000 + 1; long long v2 = v1 + rng() % 1000 + 1; a.push_back((int)v1); a.push_back((int)v2); while(v1 + v2 <= INF) { long long v3 = v1 + v2; a.push_back((int)v3); v1 = v2; v2 = v3; if(rng() % 10 == 0) break; // 随机断裂 } } while((int)a.size() < n) a.push_back(rng() % INF); } // 必须保证严格递增且不重复 sort(a.begin(), a.end()); a.erase(unique(a.begin(), a.end()), a.end()); n = a.size(); // 写入文件 fin << n << "\n"; for (int i = 0; i < n; ++i) fin << a[i] << (i == n - 1 ? "" : " "); fin.close(); // 计算标准答案 ofstream fout(out_name); fout << get_ans(n, a) << endl; fout.close(); cout << "Test Case " << tid << " generated: N=" << n << endl; } int main() { cout << "Generating 10 test cases for Longest Fibonacci-Like Subsequence..." << endl; for (int i = 1; i <= 10; ++i) { generate_test_case(i); } cout << "Done." << endl; return 0; }数据分布与复杂度分析
测试点 范围 构造特征 区分算法 1 随机分布 基础 暴力能拿分 2 无合法子序列 测试对 max_len < 3返回 0 的处理3 包含标准 链 验证最长路径检索能力 4-6 混合随机链 超时, 刚好 AC 7-10 大规模稀疏/稠密链 必须使用 (哈希或双指针) 关键设计点说明
- 区分算法复杂度:
- 是关键。 的操作次数约为 ,在 C++ 中配合哈希表查找约耗时 。
- 的操作次数约为 ,在 1 秒时限内绝对无法通过。
- 文件大小控制:
- 即使是 的测试点,每个整数约 10 字节,文件大小约 。10 组总大小约 ,远低于 的限制。
- 安全性与异常:
- 严格递增:使用了
sort+unique,满足题目“严格递增”的要求。 - 溢出处理:在生成 Fibonacci 链时使用了
long long判断,防止计算 时超过int上限导致死循环。 - 非递归实现:标程和生成器均采用循环结构,避免深度递归导致的爆栈。
- 严格递增:使用了
- 随机性:
- 使用
mt19937配合时间戳种子,保证每次生成的数据分布均匀且不重复。
- 使用
使用方法:
- 将代码保存为
gen.cpp。 - 编译运行:
g++ gen.cpp -o gen -O3 -std=c++14。 - 执行
./gen,即可在文件夹内看到 20 个标准的测试文件。
- 区分算法复杂度:
-
0
孩子,这道题是 “状态压缩”与“降维打击” 的经典博弈。在类斐波那契序列中,三元关系 是核心。
下面我按算法进阶的顺序,为你提供三个版本的完整代码,并分析其演变过程。
版本 1:暴力搜索(适合初学者,预计得分 30-50%)
思路: 既然序列由前两项决定,我们就枚举数组中任意两个数作为序列的“种子”,然后按照斐波那契规则不断向后寻找。 时间复杂度: ;空间复杂度: 。
#include <iostream> #include <vector> #include <algorithm> using namespace std; // 版本 1: 暴力枚举起点种子 O(N^3) int solve_v1(int n, vector<int>& nums) { int max_len = 0; // 枚举序列的第一项和第二项 for (int i = 0; i < n; ++i) { for (int j = i + 1; j < n; ++j) { int a = nums[i]; int b = nums[j]; int curr_len = 2; // 按照规则在剩余部分寻找 a + b // 注意:因为数组递增,可以使用 binary_search 提速至 O(N^2 log N) while (true) { int next_val = a + b; // 在 j 之后寻找 next_val if (binary_search(nums.begin() + j + 1, nums.end(), next_val)) { curr_len++; a = b; b = next_val; } else { break; } } if (curr_len >= 3) max_len = max(max_len, curr_len); } } return max_len; } int main() { int n; cin >> n; vector<int> nums(n); for (int i = 0; i < n; ++i) cin >> nums[i]; cout << solve_v1(n, nums) << endl; return 0; }
版本 2:哈希表优化 DP(资料推荐做法,预计得分 100%)
思路: 引入二维 DP。
dp[j][i]表示以索引 和 结尾的类斐波那契序列长度。 转移方程: 找到 使得 且 ,则dp[j][i] = dp[k][j] + 1。 时间复杂度: 平均 ;空间复杂度: 。#include <iostream> #include <vector> #include <unordered_map> #include <algorithm> using namespace std; // 全局静态数组,2500*2500 占用约 25MB 内存,NOIP 建议放在全局 int dp[2505][2505]; // 版本 2: 哈希表辅助 DP O(N^2) int solve_v2(int n, vector<int>& nums) { // 预处理数值到索引的映射,实现 O(1) 查找 k unordered_map<int, int> val_to_idx; for (int i = 0; i < n; ++i) val_to_idx[nums[i]] = i; int max_len = 0; // 初始化:任何两项都可以视为长度为 2 的序列起点 for (int i = 0; i < n; ++i) for (int j = 0; j < i; ++j) dp[j][i] = 2; for (int i = 0; i < n; ++i) { for (int j = 0; j < i; ++j) { int target = nums[i] - nums[j]; // 易错点:必须保证 target < nums[j] (因为数组递增,即保证索引 k < j) // 且 target 必须在数组中存在 if (target < nums[j] && val_to_idx.count(target)) { int k = val_to_idx[target]; dp[j][i] = dp[k][j] + 1; max_len = max(max_len, dp[j][i]); } } } return max_len >= 3 ? max_len : 0; } int main() { ios::sync_with_stdio(false); cin.tie(0); int n; cin >> n; vector<int> nums(n); for (int i = 0; i < n; ++i) cin >> nums[i]; cout << solve_v2(n, nums) << endl; return 0; }
版本 3:双指针优化 DP(最优常数版,适合 较大时)
思路: 既然
nums是严格递增的,对于固定的 ,当 减小时, 会增大。我们可以用左右双指针在 内处理完所有的 。 优点: 彻底抛弃哈希表,常数极小,且空间利用率高。 时间复杂度: 严格 ;空间复杂度: 。#include <iostream> #include <vector> using namespace std; int f[2505][2505]; // f[j][i] int solve_v3(int n, vector<int>& nums) { int max_len = 0; // 技巧:i 作为序列最后一项 for (int i = 2; i < n; ++i) { int left = 0, right = i - 1; while (left < right) { int sum = nums[left] + nums[right]; if (sum < nums[i]) { left++; // 和太小,左指针右移 } else if (sum > nums[i]) { right--; // 和太大,右指针左移 } else { // 刚好满足:nums[left] + nums[right] = nums[i] // 此时 k=left, j=right f[right][i] = (f[left][right] > 0 ? f[left][right] : 2) + 1; max_len = max(max_len, f[right][i]); left++; right--; } } } return max_len; } int main() { int n; cin >> n; vector<int> a(n); for(int i=0; i<n; ++i) cin >> a[i]; cout << solve_v3(n, a) << endl; return 0; }
复杂度分析思考过程
-
为什么需要二维 DP? 一维 DP 只能代表“以 结尾的最优解”。但在类斐波那契中,同样是以数字
8结尾,[1, 7, 8]和[3, 5, 8]是完全不同的序列。增加一维j(前一项)是为了锁死序列的公和关系。 -
空间换时间的权衡:
- 时,。
- 使用
int数组占用约 25MB,远低于常见的 128MB 限制。 - 如果 增加到 ,我们需要考虑使用哈希表(只存存在的对)或滚动数组(如果可能)。
时间复杂度优化建议
- 哈希表 vs 二分查找:
- 如果 范围很大但 很小,哈希表(
unordered_map)表现良好。 - 但在 NOIP 竞赛中,如果数组有序且数据密集,双指针 (Version 3) 的常数是最小的,通常比哈希表快 3-5 倍。
- 如果 范围很大但 很小,哈希表(
- 提前终止:
- 在双指针扫描时,如果
nums[left] * 2 > nums[i],说明不可能再找到满足条件的left和right(因为 ),可以提前跳出。
- 在双指针扫描时,如果
教练寄语: 孩子,记住:子序列问题的维度通常取决于它“回看”了多少项。 既然类斐波那契要看两项,那就给它两个维度。掌握了这个“升维”思想,你就掌握了复杂 DP 的命门。
-
- 1
信息
- ID
- 19492
- 时间
- 1000ms
- 内存
- 128MiB
- 难度
- 10
- 标签
- 递交数
- 5
- 已通过
- 6
- 上传者