我们面对的是一个有向无环图(DAG)上的最长路问题,或者更具体地说,是一个一维动态规划问题。
数据范围:N≤104,M≤100,∣bi∣≤105。
设 f[i] 表示到达第 i 关时,已经获得的最大总分。这里的“到达”指的是刚刚通过某些通道到达第 i 关,并且尚未加上离开第 i 关的得分 bi。
f[0] = 0。f[i] 应初始化为一个很小的负数。对于第 i 关(1≤i<N),我们可以从更靠前的某一关 k 通过某个通道 j 到达,即 k+aj=i,所以 k=i−aj。
在离开第 k 关时我们会获得 bk 分,因此:
当我们到达某关 i(0≤i<N)后,如果存在一个通道 j 使得 i+aj≥N,说明从当前关可以直接通关。通关时我们还能获得离开第 i 关的得分 bi,因此总分为 f[i] + b[i]。
最终答案即为所有可行通关方案得分的最大值:
f 数组为负无穷(例如 -0x3f3f3f3f),设 f[0] = 0。f[i] = max(f[i], f[i - a_j] + b[i - a_j])。ans 为负无穷。f[i] + b[i] 更新 ans。ans。f、a、b 数组。cpp1#include <cstdio> 2#include <cstdlib> 3#include <cstring> 4#include <algorithm> 5#include <iostream> 6using namespace std; 7 8const int N = 10005; 9const int M = 105; 10const int inf = 0x3f3f3f3f; 11int a[M], b[N], f[N]; 12 13int main() { 14 int n, m; 15 scanf("%d%d", &n, &m); 16 // 读入每个通道的前进量 17 for (int i = 1; i <= m; ++i) 18 scanf("%d", &a[i]); 19 // 读入离开每关的得分 20 for (int i = 0; i < n; ++i) 21 scanf("%d", &b[i]); 22 23 // 初始化 f 为负无穷,表示尚未到达 24 memset(f, -0x3f, sizeof(f)); 25 f[0] = 0; // 初始在第0关,得分0 26 27 // DP:按关卡顺序递推 28 for (int i = 1; i < n; ++i) { 29 for (int j = 1; j <= m; ++j) { 30 if (i - a[j] >= 0) { 31 f[i] = max(f[i], f[i - a[j]] + b[i - a[j]]); 32 } 33 } 34 } 35 36 // 统计答案:从可以通关的关卡中选择最大得分 37 int ans = -inf; 38 for (int i = 0; i < n; ++i) { 39 for (int j = 1; j <= m; ++j) { 40 if (i + a[j] >= n) { 41 ans = max(ans, f[i] + b[i]); 42 break; // 找到一个能通关的通道即可,无需继续枚举 43 } 44 } 45 } 46 47 cout << ans << endl; 48 return 0; 49}
memset(f, -0x3f, sizeof(f)) 会将每个字节设为 0x3f,从而让 int 型变量变成非常小的负数,表示“无法到达”。同时也保证了在取 max 时不会被无效状态干扰。i 从小到大枚举,保证了计算 f[i] 时所有 i - a_j 的结果已经求出,满足动态规划的无后效性。i + a[j] >= n 就跳出,因为只需确认该关卡能否直接通关,不必继续浪费循环。f[i] + b[i] 中体现。本题本质上是一个带有“通关跳跃”性质的简单动态规划问题。关键在于正确理解“到达”与“离开”得分之间的关系,将状态定义为到达第 i 关的累计得分(不含离开 i 的得分),最后特判通关条件并补充最后一份得分即可。由于数据范围不大,O(NM) 的 DP 可以轻松通过所有测试点。