[POI 2002 R1] 火车线路

GitHub跳转原题关系图返回列表

把城市之间的每一段铁路看成一个位置,请求对应区间 `[O,D-1]` 的整体加法,能否接单只取决于这段区间的最大占用是否超过座位数。

OJ: luogu

题目 ID: P8856

难度:普及/提高-

标签:线段树区间最大值区间加建模

日期: 2026-06-21 02:39

题意

火车从 1 号城市开到 C 号城市,一共有 S 个座位。

现在有 R 个订票请求,每个请求是:

  • 上车站 O
  • 下车站 D
  • 需要 N 个座位

按顺序处理这些请求。

如果这批乘客经过的整段路线都还能塞下 N 个座位,就接受这单;否则拒绝。

输出:

  • T 表示接受
  • N 表示拒绝

思路

先看一个最容易理解的暴力:

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

// brute.cpp:直接维护每一段铁路当前已占用的座位数。

const int MAXC = 60000 + 5;

int used[MAXC];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int city_cnt, seat_cnt, req_cnt;
    cin >> city_cnt >> seat_cnt >> req_cnt;

    for (int i = 1; i <= req_cnt; i++) {
        int o, d, n;
        cin >> o >> d >> n;

        int mx = 0;
        for (int j = o; j <= d - 1; j++) {
            mx = max(mx, used[j]);
        }

        if (mx + n <= seat_cnt) {
            cout << "T\n";
            for (int j = o; j <= d - 1; j++) {
                used[j] += n;
            }
        } else {
            cout << "N\n";
        }
    }

    return 0;
}

brute.cpp 直接维护每一段铁路当前被占了多少座位。

对于请求 (O, D, N)

  • 先扫描 [O, D-1],看最大占用是多少
  • 若还能装下,再把这段全部加上 N

这个思路完全正确,但每次都扫整段,复杂度太高。

真正的建模很直接:

把城市之间的铁路段看成数组位置:

  • i 个位置表示城市 ii+1 这一段铁路

那么一个请求 (O, D, N) 就会影响区间:

[O, D-1]

而能否接受,只取决于这段区间里当前的最大占用 mx

  • mx + N <= S,说明整段铁路都还能装下这批乘客
  • 否则至少有一段超载,必须拒绝

于是题目就变成了标准的:

  • 区间最大值查询
  • 区间加法修改

用懒标记线段树维护即可。

代码

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

const int MAXC = 60000 + 5;

int city_cnt, seat_cnt, req_cnt;
int seg_max[MAXC << 2];
int lazy_add[MAXC << 2];

void push_up(int u) {
    seg_max[u] = max(seg_max[u << 1], seg_max[u << 1 | 1]);
}

void apply_add(int u, int val) {
    seg_max[u] += val;
    lazy_add[u] += val;
}

void push_down(int u) {
    if (lazy_add[u] == 0) {
        return;
    }
    apply_add(u << 1, lazy_add[u]);
    apply_add(u << 1 | 1, lazy_add[u]);
    lazy_add[u] = 0;
}

void range_add(int u, int l, int r, int ql, int qr, int val) {
    if (ql <= l && r <= qr) {
        apply_add(u, val);
        return;
    }

    push_down(u);
    int mid = (l + r) >> 1;
    if (ql <= mid) {
        range_add(u << 1, l, mid, ql, qr, val);
    }
    if (qr > mid) {
        range_add(u << 1 | 1, mid + 1, r, ql, qr, val);
    }
    push_up(u);
}

int query_max(int u, int l, int r, int ql, int qr) {
    if (ql <= l && r <= qr) {
        return seg_max[u];
    }

    push_down(u);
    int mid = (l + r) >> 1;
    int ans = 0;
    if (ql <= mid) {
        ans = max(ans, query_max(u << 1, l, mid, ql, qr));
    }
    if (qr > mid) {
        ans = max(ans, query_max(u << 1 | 1, mid + 1, r, ql, qr));
    }
    return ans;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    cin >> city_cnt >> seat_cnt >> req_cnt;
    for (int i = 1; i <= req_cnt; i++) {
        int o, d, n;
        cin >> o >> d >> n;

        // 乘客会占用区间 [o, d-1] 上的每一段铁路。
        int current_max = query_max(1, 1, city_cnt - 1, o, d - 1);
        if (current_max + n <= seat_cnt) {
            cout << "T\n";
            range_add(1, 1, city_cnt - 1, o, d - 1, n);
        } else {
            cout << "N\n";
        }
    }

    return 0;
}

复杂度

每个请求需要:

  • 一次区间最大值查询
  • 最多一次区间加法修改

所以单次复杂度:

O(logC)O(log C)

总时间复杂度:

O(RlogC)O(R log C)

空间复杂度:

O(C)O(C)

总结

这题最核心的点是把“城市区间订票”转成“铁路段数组上的区间容量维护”。

一旦完成这个转换,剩下就是标准线段树模板。

一图流解析

这张图把本题的建模、关键转移、实现检查和训练方法压缩到一页,适合读完正文后复盘。

一图流解析