✏️ 纠错
第 153 题 / 共 155 题

题目描述

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

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

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

输入格式

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

接下来 n 行,每行两个正整数 xi​,ti​,分别表示金币出现的坐标与时刻。

输出格式

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

输入输出样例

输入 #1

3
1 6
3 7
2 4

输出 #1

2

输入 #2

4
1 1
2 2
1 3
2 4

输出 #2

3

说明/提示

对于 40% 的测试点,保证 1≤n≤8。

对于另外 30% 的测试点,保证 1≤n≤100,1≤xi​≤100,1≤ti​≤100。

对于所有测试点,保证 1≤n≤105,1≤xi​≤109,1≤ti​≤109。

📝 题目解析

题目分析

问题核心:小A只能向右移动或静止,求最多能收集的金币数(需在时刻t恰好位于x)。

约束条件
若收集金币i和j(i在j之前),需满足:

1.xi≤xj(只能右移,位置不下降);

2.tj−ti≥xj−xi(时间差足够移动距离)。

化简条件2得:tj−xj≥ti−xi

转化与解法
对金币按xi排序后,问题等价于求ti−xi的「最长非递减子序列」长度(动态规划+二分优化)

代码注释


#include <algorithm>
#include <cstdio>
#include <climits>
using namespace std;

const int N = 1e5 + 5; // 最大金币数
int n;
int x[N], t[N];        // 金币i:位置x[i],时间t[i]
int p[N];              // 排序用的索引数组
long long f[N];        // 用long long避免溢出,f[k]表示长度为k的序列末尾的t-x最小值

// 排序规则:先按x升序,x相同时按(t-x)升序;x不同时,确保t-x非递减
bool cmp(int a, int b) {
    if (x[a] != x[b]) return x[a] < x[b];
    return (t[a] - x[a]) < (t[b] - x[b]);
}

int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d%d", &x[i], &t[i]);
        p[i] = i;
    }

    // 初始化f数组为无穷大(表示初始时这些长度的序列不存在)
    fill(f, f + N, LLONG_MAX);
    f[0] = 0; // 长度为0的序列:时刻0,位置0,t-x=0-0=0
    int mx = 0; // 最长序列长度

    // 按x升序排序,x相同则按(t-x)升序
    sort(p + 1, p + n + 1, cmp);

    for (int i = 1; i <= n; i++) {
        int idx = p[i];
        long long xi = x[idx];
        long long ti = t[idx];
        long long v = ti - xi; // 核心判断值:必须>=0且非递减

        // 基础条件:t[i] >= x[i](否则无法到达)
        if (v < 0) continue;

        // 二分查找:找到最大的k,使得f[k] <= v
        int l = 0, r = mx;
        while (l < r) {
            int mid = (l + r + 1) / 2; // 向上取整避免死循环
            if (f[mid] <= v) { // 原代码此处符号写反,修正为<=
                l = mid;
            } else {
                r = mid - 1;
            }
        }

        // 尝试更新长度为l+1的序列的末尾值
        if (v < f[l + 1]) {
            f[l + 1] = v;
        }
        // 更新最长序列长度
        if (l + 1 > mx) {
            mx = l + 1;
        }
    }

    printf("%d\n", mx);
    return 0;
}
 

复杂度分析

时间复杂度:O(nlogn),排序耗时O(nlogn),二分查找总耗时O(nlogn),适用于n≤1e5的数据范围。

空间复杂度:O(n),用于存储金币信息和动态规划数组。