2025.4.25

线性 DP 原理与实战解析
**
动态规划(Dynamic Programming,简称 DP)是一种通过将原问题分解为相对简单的子问题,并保存子问题的解来避免重复计算,从而高效求解复杂问题的算法策略。在线性 DP 中,状态之间的依赖关系呈现出线性结构,各状态按照一定的线性顺序相互递推,最终得出问题的最优解或结果状态。这种方式通过记录中间状态的结果,避免了大量重复计算,大大提升了算法的效率。
题目一:线性 DP 在时间 - 空间约束问题中的应用
题目描述
给定(m)个事件,每个事件(i)有发生时间(t[i]),以及在二维平面上的坐标((x[i], y[i]))。从一个事件到另一个事件(j)到(i),所需的时间至少为两点之间的曼哈顿距离(|x[i] - x[j]| + |y[i] - y[j]|) 。要求找出在时间和空间约束下,最多能按顺序参与的事件数量。
解题思路
本题可使用线性 DP 解决。定义状态(dp[i])表示以第(i)个事件结尾时,最多能参与的事件数量。对于每个事件(i),遍历前面的事件(j)((1 \leq j < i)),如果从事件(j)到事件(i)所需的时间不超过(t[i] - t[j]),即(|x[i] - x[j]| + |y[i] - y[j]| \leq t[i] - t[j]),则可以从(dp[j])转移到(dp[i]),并且更新(dp[i] = \max(dp[j] + 1, dp[i])) 。最后遍历所有的(dp[i]),找出其中的最大值即为答案。
代码实现

include

include

include

include

using namespace std;
typedef long long LL;
const int N = 1e4 + 50;
int dp[N]; // dp[i]表示以第i个事件结尾时,最多能参与的事件数量
int t[N], x[N], y[N]; // 分别存储事件的时间、x坐标、y坐标

int main() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> t[i] >> x[i] >> y[i];
}
for (int i = 1; i <= m; i++) {
dp[i] = 1; // 初始化,每个事件自身至少算一个
for (int j = 1; j < i; j++) {
// 判断从事件j到事件i是否满足时间和空间约束
if (abs(x[i] - x[j]) + abs(y[i] - y[j]) <= t[i] - t[j]) {
dp[i] = max(dp[j] + 1, dp[i]);
}
}
}
int ma = 0;
for (int i = 1; i <= m; i++) {
ma = max(dp[i], ma);
}
cout << ma;
return 0;
}

题目二:线性 DP 在等差数列计数问题中的应用
题目描述
给定一个长度为(n)的整数数列(A),要求统计数列中所有等差数列的个数。这里一个数本身也算作一个等差数列,若几个数之间公差确定,以最后一个数结尾的等差数列个数是倒数第二个数的等差数列个数加 1。
解题思路
本题同样采用线性 DP 求解。定义二维状态数组(dp[i][d + 20000]),其中(i)表示数列的第(i)个元素,(d)表示公差,由于公差可能为负数,为了方便数组索引,将公差加上偏移量(20000) 。(dp[i][d + 20000])表示以第(i)个数结尾,公差为(d)的等差数列的个数。
对于每个数(A[i]),遍历前面的数(A[j])((1 \leq j < i)),计算公差(d = A[i] - A[j]),此时(dp[i][d + 20000])可以从(dp[j][d + 20000])转移过来,并且加上 1(因为新加入了第(i)个数),即(dp[i][d + 20000]=(dp[i][d + 20000]+1+dp[j][d + 20000])%MOD) 。同时,将(dp[j][d + 20000])累加到结果(res)中,并且每个数自身也算一个等差数列,所以(res)初始化为(n),后续不断累加符合条件的等差数列个数。
代码实现

include

include

include

include

using namespace std;
typedef long long LL;
const int N = 1e3 + 50;
const int MOD = 998244353;
const int OFFSET = 20000; // 公差偏移量
LL dp[N][2 * OFFSET + 20]; // dp[i][d + OFFSET]表示以第i个数结尾,公差为d的等差数列的个数
LL A[N]; // 存储数列

int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> A[i];
}
LL res = n; // 每个数自身算一个等差数列,初始化结果
for (int i = 1; i <= n; i++) {
for (int j = 1; j < i; j++) {
int d = A[i] - A[j]; // 计算公差
// 更新以第i个数结尾,公差为d的等差数列个数
dp[i][d + OFFSET] = (dp[i][d + OFFSET] + 1 + dp[j][d + OFFSET]) % MOD;
// 将符合条件的等差数列个数累加到结果中
res = (res + dp[j][d + OFFSET]) % MOD;
}
}
cout << res;
return 0;
}

posted @ 2025-04-25 15:59  大门牙很好  阅读(29)  评论(0)    收藏  举报