查看: 30|回复: 0

动态规划经典应用:2022年CSP-J上升点列问题详解与代码实现

[复制链接]
  • TA的每日心情
    无聊
    昨天 10:23
  • 签到天数: 52 天

    [LV.5]常住居民I

    51

    主题

    0

    回帖

    86

    积分

    注册会员

    Rank: 2

    积分
    86
    发表于 2025-7-23 13:48:44 | 显示全部楼层 |阅读模式
    一、问题理解与算法思路
    题目要求在一组二维坐标点中,找出最长的严格递增序列(x和y都严格递增),允许插入最多k个额外点来连接不相邻的点。这个问题可以抽象为在二维平面上的路径规划问题,需要找到最优的连接方式。
    二、完整代码实现(带详细注释)
    1. #include <iostream>
    2. #include <vector>
    3. #include <algorithm>
    4. using namespace std;

    5. // 定义二维点结构体
    6. struct Point {
    7.     int x, y;
    8.     // 重载比较运算符,用于排序
    9.     bool operator<(const Point& other) const {
    10.         return x < other.x || (x == other.x && y < other.y);
    11.     }
    12. };

    13. int main() {
    14.     // 输入输出优化
    15.     ios::sync_with_stdio(false);
    16.     cin.tie(nullptr);
    17.    
    18.     int n, k;
    19.     cin >> n >> k;  // 输入点数和可添加点数
    20.    
    21.     vector<Point> points(n);
    22.     for (int i = 0; i < n; ++i) {
    23.         cin >> points[i].x >> points[i].y;  // 输入每个点的坐标
    24.     }
    25.    
    26.     // 对点集进行排序:先按x升序,x相同则按y升序
    27.     sort(points.begin(), points.end());
    28.    
    29.     // 动态规划表初始化
    30.     // dp[i][j]表示以第i个点结尾,使用j个额外点时的最长序列长度
    31.     vector<vector<int>> dp(n, vector<int>(k + 1, 1));
    32.     int max_len = 1;  // 记录全局最大长度
    33.    
    34.     // 动态规划主循环
    35.     for (int i = 0; i < n; ++i) {  // 遍历每个点作为终点
    36.         for (int j = 0; j <= k; ++j) {  // 遍历可能的额外点数
    37.             dp[i][j] = 1;  // 初始化为只包含当前点
    38.             
    39.             for (int prev = 0; prev < i; ++prev) {  // 检查所有可能的前驱点
    40.                 // 确保前驱点在当前点左下方
    41.                 if (points[prev].x > points[i].x || points[prev].y > points[i].y) {
    42.                     continue;
    43.                 }
    44.                
    45.                 // 计算两个点之间需要插入的点数
    46.                 int dx = points[i].x - points[prev].x;
    47.                 int dy = points[i].y - points[prev].y;
    48.                 int needed = dx + dy - 1;  // 需要插入的点数
    49.                
    50.                 // 如果当前可用点数足够
    51.                 if (needed <= j) {
    52.                     // 状态转移方程
    53.                     dp[i][j] = max(dp[i][j], dp[prev][j - needed] + dx + dy);
    54.                 }
    55.             }
    56.             
    57.             // 考虑使用剩余的k-j个点来延长序列
    58.             max_len = max(max_len, dp[i][j] + (k - j));
    59.         }
    60.     }
    61.    
    62.     cout << max_len << endl;  // 输出结果
    63.     return 0;
    64. }
    复制代码


    三、算法核心解析
    • 预处理排序:将点按x坐标升序排列,x相同时按y升序排列,确保后续处理的有序性。
    • 动态规划状态定义

      • dp[j]:表示以第i个点结尾,使用j个额外点时的最长序列长度
      • 初始状态:每个点单独构成序列,长度为1

    • 状态转移过程

      • 对于每个点i,检查所有可能的前驱点prev
      • 确保prev点在i点的左下方(x和y都更小)
      • 计算两点间需要插入的点数needed = dx + dy - 1
      • 如果可用点数j >= needed,则更新dp[j]

    • 全局最大值更新

      • 考虑使用剩余的k-j个点来延长当前序列
      • 实时更新max_len记录全局最大值

    四、复杂度分析与优化
    • 时间复杂度:O(n²k)
    • 空间复杂度:O(nk)
    • 可能的优化方向:

    五、学习要点总结
    • 理解动态规划在多维状态下的应用
    • 掌握二维问题的状态定义技巧
    • 学会处理带约束条件的动态规划问题
    • 理解排序预处理在算法中的重要性

    来源:动态规划经典应用:2022年CSP-J上升点列问题详解与代码实现

    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    快速回复 返回顶部 返回列表