题解:洛谷 P14078 金币收集

【题目来源】

洛谷:P14078 [GESP202509 七级] 金币收集 - 洛谷

【题目描述】

小 A 正在游玩收集金币的游戏。具体来说,在数轴上将会出现 \(n\) 枚金币,其中第 \(i\) 枚(\(1≤i≤n\))金币将会在时刻 \(t_i\) 出现在数轴上坐标为 \(x_i\) 的位置。小 A 必须在时刻 \(t_i\) 恰好位于坐标 \(x_i\),才可以获得第 \(i\) 枚金币。

游戏开始时为时刻 \(0\),此时小 A 的坐标为 \(0\)。正常来说,小 A 可以按游戏机的按键在数轴上左右移动,但不幸的是游戏机的左方向键失灵了。小 A 每个时刻只能选择保持不动,或是向右移动一个单位。换言之,如果小 A 在时刻 \(t\) 的坐标为 \(x\),那么他在时刻 \(t+1\) 的坐标只能是 \(x\) 或是 \(x+1\) 二者之一,分别对应保持不动和向右移动。

小 A 想知道他最多能收集多少枚金币。你能帮他收集最多的金币吗?

【输入】

第一行,一个正整数 \(n\),表示金币的数量。

接下来 \(n\) 行,每行两个正整数 \(x_i,t_i\),分别表示金币出现的坐标与时刻。

【输出】

输出一行,一个整数,表示小 A 最多能收集的金币数量。

【输入样例】

3
1 6
3 7
2 4

【输出样例】

2

【核心思想】

  1. 问题分析:小 A 在数轴上从时刻 \(0\)、坐标 \(0\) 出发,每个时刻只能保持不动或向右移动一个单位。金币 \(i\) 在时刻 \(t_i\) 出现在坐标 \(x_i\),必须在恰好时刻 \(t_i\) 位于 \(x_i\) 才能收集。求最多能收集的金币数量。由于只能向右移动,小 A 的坐标随时间单调不减,且从 \((x_i, t_i)\) 能到达 \((x_j, t_j)\)\(t_i \le t_j\))的条件是 \(x_j - x_i \le t_j - t_i\),即 \(x_j - t_j \le x_i - t_i\)。此外,从原点出发能到达金币 \(i\) 的前提是 \(x_i \le t_i\)

  2. 算法选择

    • 线性 DP + 二分(类似 LIS):将问题转化为在筛选后的金币序列中,寻找最长的子序列,使得相邻两个金币满足 \(x_j \ge x_i\)\(x_j - t_j \le x_i - t_i\)。这类似于最长递增子序列(LIS),但比较条件变为 \(x - t\) 的非递增性。使用二分维护当前最优序列,对于每个新金币,若它能接在序列末尾则直接加入;否则找到第一个满足 \(x - t > x_{mid} - t_{mid}\) 的位置并替换,保证序列始终最优。
    • 排序预处理:先按 \(x\) 升序排序,\(x\) 相同时按 \(t\) 升序,保证处理顺序满足坐标单调性。
  3. 关键步骤

    • 筛选有效金币:读入 \(n\) 个金币,只保留满足 \(x_i \le t_i\) 的金币(否则从原点无法到达),存入数组 \(a\)
    • 排序:对有效金币按 \(x\) 升序、\(t\) 升序排序。
    • 初始化最长序列:用数组 \(b\) 维护当前最优序列,\(b[1] = a[1]\),长度 \(len = 1\)
    • 遍历与更新:对于 \(i\)\(2\)\(cur\)
      • \(a[i].x - b[len].x \le a[i].t - b[len].t\)(即可接在末尾),则 \(b[++len] = a[i]\)
      • 否则,二分查找第一个满足 \(a[i].x - b[mid].x > a[i].t - b[mid].t\) 的位置 \(j\),用 \(a[i]\) 替换 \(b[j]\)
    • 输出答案\(len\) 即为最多能收集的金币数。
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \log n)\),排序 \(O(n \log n)\),每个元素二分 \(O(\log n)\)
    • 空间复杂度:\(O(n)\),存储筛选后的金币和最优序列。
  5. 线性 DP + 二分的核心思想

    • 可达性条件转化:从 \((x_i, t_i)\)\((x_j, t_j)\) 的可达性条件 \(x_j - x_i \le t_j - t_i\) 可转化为 \(x_j - t_j \le x_i - t_i\),这使得比较变得简洁。
    • LIS 思想的推广:问题本质是在二维偏序下求最长链,通过排序固定一维(\(x\)),另一维转化为 \(x - t\) 的非递增序列,用二分维护最长序列。
    • 贪心替换的合理性:当新元素无法接在末尾时,用它替换第一个不满足条件的位置,不会使后续可接的元素变少,这是 LIS 二分解法的经典性质。
    • 适用场景:适用于二维偏序下的最长链问题,如导弹拦截、任务调度等需要同时满足两个维度约束的场景。

【算法标签】

普及+ #线性DP-一维

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 100005;  // 定义最大数据量

int n;                  // 输入的数据对数
int maxn;               // 未使用的变量(可删除)
int len;                // 记录当前最长序列长度

// 定义节点结构体,存储x和t值
struct Node
{
    int x, t;
} a[N], b[N];           // a数组存储原始数据,b数组存储最长序列

/**
 * 比较函数,用于排序
 * @param x 第一个节点
 * @param y 第二个节点
 * @return 先按x升序,x相同按t升序
 */
bool cmp(Node x, Node y)
{
    if (x.x != y.x)
        return x.x < y.x;
    return x.t < y.t;
}

/**
 * 二分查找函数,寻找插入位置
 * @param x 要查找的节点
 * @return 合适的插入位置
 */
int find(Node x)
{
    int L = 1, R = len, mid;
    while (L < R)
    {
        mid = (L + R) / 2;
        if (x.x - b[mid].x > x.t - b[mid].t)
            R = mid;
        else
            L = mid + 1;
    }
    return L;
}

int main()
{
    cin >> n;
    int cur = 0;  // 记录有效数据数量

    // 输入并筛选有效数据(x <= t)
    for (int i = 1; i <= n; i++)
    {
        int x, t;
        cin >> x >> t;
        if (x <= t)
        {
            a[++cur].x = x;
            a[cur].t = t;
        }
    }

    // 对有效数据进行排序
    sort(a + 1, a + cur + 1, cmp);

    // 初始化最长序列
    len = 1;
    b[1] = a[1];

    // 构建最长满足条件的序列
    for (int i = 2; i <= cur; i++)
    {
        // 如果当前元素可以直接加入序列
        if (a[i].x - b[len].x <= a[i].t - b[len].t)
        {
            b[++len] = a[i];
        }
        // 否则找到合适的位置替换
        else
        {
            int j = find(a[i]);
            b[j] = a[i];
        }
    }

    // 输出最长序列长度
    cout << len << endl;

    return 0;
}

【运行结果】

3
1 6
3 7
2 4
2
posted @ 2026-09-13 18:26  团爸讲算法  阅读(6)  评论(0)    收藏  举报