top1编程
← 返回题目
题解

上升点列

1 条题解

  • 0
    @ 2026-8-7 15:19:56

    P4913 上升点列(提高)

    解题思路

    第一步,理解题意。 平面上有n个整数点,我们还可以另外添加k个整数点。添加完以后,要从这些点里选出一列点排成一个序列,要求任意相邻两个点的距离恰好是1,并且横坐标、纵坐标都单调不减(也就是每一步只能向右走一格或向上走一格)。问能排出的序列最长有多长。

    第二步,排序简化问题。 因为序列每一步只能向右或向上走,所以序列里的点按横坐标从小到大(横坐标相同时按纵坐标从小到大)排列,正好就是序列的顺序。我们先把所有给定点排序。

    第三步,设计DP状态。 定义dp[i][j]表示:以第i个给定点为序列的最后一个点,并且已经用了j个添加点的时候,序列的最大长度。初始时,序列只有第i个点自己,长度是1,即dp[i][0]=1。

    第四步,写出转移。 从第t个点走到第i个点,需要先向右走(x[i]-x[t])步,再向上走(y[i]-y[t])步,两点之间的中间点一共有need=(x[i]-x[t])+(y[i]-y[t])-1个。如果x[t]>x[i]或者y[t]>y[i],就不能从t走到i;如果need大于k也跳过。转移时,从状态dp[t][j-need]加上这need个中间点和第i个点,序列长度增加need+1,即dp[i][j]取max(dp[t][j-need]+need+1)。

    第五步,统计答案。 答案就是所有dp[i][j]中的最大值。因为j不能超过k,所以内层循环j从need到k。坐标可能很大,need用long long计算防止溢出。样例n=8、k=2时,最长序列是8,与样例输出一致。

    参考代码

    // 上升点列:排序后DP,dp[i][j]表示以第i个点为序列最后一个给定点、用了j个添加点时的最长长度
    #include <iostream>
    #include <algorithm>
    using namespace std;
    struct Pt { long long x, y; };
    Pt p[505];
    int dp[505][105];
    bool cmp(const Pt& a, const Pt& b) {
        if (a.x != b.x) return a.x < b.x;
        return a.y < b.y;
    }
    int main() {
        int n, k;
        cin >> n >> k;
        for (int i = 0; i < n; i++) cin >> p[i].x >> p[i].y;
        sort(p, p + n, cmp);
        int ans = 0;
        for (int i = 0; i < n; i++) {
            dp[i][0] = 1; // 序列只有一个点,不需要添加点
            if (dp[i][0] > ans) ans = dp[i][0];
            for (int t = 0; t < i; t++) {
                // 只有t在i的左下角才能从t走到i
                if (p[t].x > p[i].x || p[t].y > p[i].y) continue;
                long long need = (p[i].x - p[t].x) + (p[i].y - p[t].y) - 1;
                if (need < 0 || need > k) continue;
                int nd = (int)need;
                // 从t到i之间要补need个点,整体长度增加need+1
                for (int j = nd; j <= k; j++) {
                    if (dp[t][j - nd] > 0) {
                        int v = dp[t][j - nd] + nd + 1;
                        if (v > dp[i][j]) {
                            dp[i][j] = v;
                            if (v > ans) ans = v;
                        }
                    }
                }
            }
        }
        cout << ans << endl;
        return 0;
    }
    

    复杂度分析

    排序需要O(n log n)。DP部分有两层枚举点t和i,共O(n²)种配对,每个配对又要枚举j从0到k,所以总时间复杂度O(n²·k)。n最大500,k最大100,500×500×100约2500万,可以轻松通过。dp数组大小n×(k+1),空间复杂度O(n·k)。

    • 1