← 題庫 / Archive
2026-05-30 Daily Hard ArrayBinary SearchBinary Indexed TreeSegment Tree

3161. Block Placement Queries

題目 / Problem

中文 有一條無限長的數線,原點在 0,向正向延伸。給你一個查詢陣列 queries,有兩種查詢:

  1. [1, x]:在距離原點 x 處放一個障礙物(保證此時 x 處還沒有障礙物)。
  2. [2, x, sz]:問能不能在 [0, x] 這段範圍內放下一個長度為 sz 的方塊,方塊必須完全落在 [0, x] 內。方塊不能與任何障礙物相交,但可以緊貼(碰到)障礙物。注意只是「詢問」,並不會真的放下方塊,每個查詢互相獨立。

回傳一個布林陣列,第 i 個 type-2 查詢能放下就是 true,否則 false

English There is an infinite number line starting at origin 0, extending in the positive direction. You are given queries with two types:

  1. [1, x]: place an obstacle at distance x (guaranteed empty there at that moment).
  2. [2, x, sz]: can you place a block of length sz somewhere inside [0, x] so it lies entirely within [0, x]? The block may not intersect any obstacle but may touch it. You don't actually place it; queries are independent.

Return a boolean array of answers for the type-2 queries.

Constraints - 1 <= queries.length <= 1.5 * 10^5 - 1 <= x, sz <= min(5 * 10^4, 3 * queries.length) - For type-1, x is always empty when asked. At least one type-2 query exists.

Worked example queries = [[1,2],[2,3,3],[2,3,1],[2,2,2]][false, true, true] After placing an obstacle at x = 2, the largest block that fits before x = 3 has size 2, so size 3 fails but sizes 1 and 2 succeed.

名詞解釋 / Glossary

  • 障礙物 / obstacle:數線上某個座標被佔住的點。方塊不能蓋過它,但端點可以剛好碰到它。An occupied point; a block may touch but not overlap it.
  • 間隙 / gap:兩個相鄰障礙物之間(或從 0 到第一個障礙物之間)的一段空白長度,方塊能塞進去的最大尺寸。The empty span between two consecutive obstacles (or from 0 to the first obstacle).
  • 線段樹 / segment tree:一種把陣列建成二元樹的資料結構,支援「單點修改」與「區間查詢(這裡是區間最大值)」,每次操作都是 O(log n)。A binary-tree structure over an array supporting point updates and range queries (here, range maximum) in O(log n).
  • 點更新 / point update:只改某一個位置的值。Change the value at a single index.
  • 區間最大查詢 / range-max query:問某段區間 [l, r] 裡的最大值。Ask for the maximum value over an index range.
  • 離線處理 / offline processing:先把所有查詢讀進來,再用對我們最方便的順序處理,而不是一邊讀一邊答。Read all queries first, then answer them in whatever order is most convenient.
  • 逆向處理 / reverse processing:從最後一個查詢往前處理。本題正向是「插入障礙物會把一個大間隙切成兩個」(難維護最大值),逆向則是「移除障礙物會把兩個間隙合併成一個」(好維護)。Walk queries from last to first, turning hard insertions into easy merges.
  • 雙向鏈結串列 / doubly linked list:每個元素記住它的「前一個」「後一個」,可以 O(1) 找到鄰居並在刪除時重接。Each node knows its previous/next neighbor, so deletion and neighbor lookup are O(1).

思路

先想暴力法。對每個 type-2 查詢 [2, x, sz],我們要判斷 [0, x] 裡是否存在一段長度 >= sz 的空白。最直接的做法是把目前所有障礙物排序,掃過 [0, x] 內每一對相鄰障礙物算間隙,再看有沒有間隙夠大。但查詢數量可達 1.5 * 10^5,每次又要掃過所有障礙物,最壞是 O(Q^2),會超時。

關鍵觀察:能不能放下方塊,只取決於 [0, x] 內的「最大空白」。空白有兩種來源。第一種是兩個相鄰障礙物之間的間隙 [a, b],只要右端點 b <= x,這段就完整落在 [0, x] 內,可容納長度 b - a 的方塊(從 0 到第一個障礙物的那段也算,相當於前一個障礙物在座標 0)。第二種是「尾段空白」:設 x 左邊最近的障礙物在 p,那麼 [p, x] 之間沒有障礙物,可放下 x - p 的方塊;若 x 左邊根本沒有障礙物,整段 [0, x] 都空著,能放 x。把這兩種的最大值拿來和 sz 比較即可。

剩下的問題是怎麼快速維護這些間隙。麻煩在於 type-1 是「插入」障礙物,插入會把一個大間隙切成兩個小間隙——要在資料結構裡同時「減少一個值、增加一個值」並維護最大值並不直觀。於是改用離線逆向:先把所有會出現的障礙物全部放好,從最後一個查詢往前掃,遇到 type-1 就把那個障礙物移除。移除剛好是插入的反操作——它把兩個相鄰間隙合併成一個,用雙向鏈結串列 O(1) 找到左右鄰居即可重接。我們用兩棵線段樹:segGap 在每個障礙物座標 p 存「p 與它左鄰障礙物之間的間隙」,查詢 [1, x] 的區間最大值就得到第一種空白;segPos 在每個障礙物座標 p 存值 p,查詢 [1, x] 的最大值就得到「不超過 x 的最近障礙物座標」,用來算尾段空白。每個 type-2 查詢就是兩次 O(log) 查詢。

Start with brute force. For each type-2 query [2, x, sz] we ask whether [0, x] contains an empty span of length >= sz. The naive way sorts the current obstacles and scans every consecutive pair inside [0, x]. With up to 1.5 * 10^5 queries each scanning all obstacles, that's O(Q^2) — too slow.

The key insight: the answer depends only on the largest empty span inside [0, x]. Empty spans come in two flavors. First, the gap [a, b] between two consecutive obstacles: as long as the right end b <= x, that whole gap lies in [0, x] and fits a block of length b - a (the span from 0 to the first obstacle counts too — treat the "previous obstacle" of the first one as coordinate 0). Second, the trailing span: if the nearest obstacle at or before x is at p, then [p, x] is empty and fits a block of x - p; if no obstacle is <= x, all of [0, x] is free and fits x. Take the max of the two and compare with sz.

The remaining difficulty is maintaining these gaps efficiently. Type-1 is an insertion, and an insertion splits one big gap into two — awkward, because it both decreases and increases values while we track a maximum. So we go offline and reverse: place every obstacle that will ever appear up front, walk queries from last to first, and on a type-1 query remove that obstacle. Removal is the inverse of insertion — it merges two neighboring gaps into one, and with a doubly linked list we find the left/right neighbors in O(1) and relink. Two segment trees do the rest: segGap stores at each obstacle position p the gap between p and its left neighbor (a range-max over [1, x] gives the first kind of span); segPos stores value p at position p (a range-max over [1, x] gives the nearest obstacle <= x, used for the trailing span). Each type-2 query is then two O(log) lookups.

逐步走查 / Walkthrough

用第一個範例 queries = [[1,2],[2,3,3],[2,3,1],[2,2,2]],預期輸出 [false, true, true]。 Using the first sample queries = [[1,2],[2,3,3],[2,3,1],[2,2,2]], expected [false, true, true].

Setup / 初始化 - 所有障礙物 / all obstacles ever placed: {2}(來自 [1,2])。最大座標 maxX = 3。 - 一開始全部放好 / start with all obstacles present: {2}. - segGap:在座標 2 存 2 - 0 = 2(左邊沒有障礙物,視為座標 0)。segGap at pos 2 = 2. - segPos:在座標 2 存 2segPos at pos 2 = 2. - 三個 type-2 查詢對應輸出槽 / output slots: query1→slot0, query2→slot1, query3→slot2.

逆向處理(從 index 3 到 index 0)/ process in reverse:

step query (index) 動作 / action segPos.query(1,x) = lastObs trailing = x - lastObs segGap.query(1,x) = internalMax best best ≥ sz ? 寫入 / write
1 [2,2,2] (i=3) 查詢 / query, x=2, sz=2 2 2-2=0 2 2 2≥2true slot2 = true
2 [2,3,1] (i=2) 查詢 / query, x=3, sz=1 2 3-2=1 2 2 2≥1true slot1 = true
3 [2,3,3] (i=1) 查詢 / query, x=3, sz=3 2 3-2=1 2 2 2≥3false slot0 = false
4 [1,2] (i=0) 移除座標 2 / remove obstacle 2:segGap[2]=0, segPos[2]=0

最後輸出槽 [slot0, slot1, slot2] = [false, true, true]。✓ Final slots [false, true, true]. ✓

注意 step 1 為什麼 [2,2,2] 是 true:障礙物在座標 2,間隙 [0, 2] 長度 2,方塊從 0 放到 2 剛好碰到障礙物(碰到允許),所以放得下。 Why step 1 is true: the gap [0, 2] has length 2; a block from 0 to 2 just touches the obstacle (touching is allowed), so it fits.

Solution — C

#include <stdlib.h>
#include <stdbool.h>

/*
 * 演算法 / Algorithm:
 *  離線 + 逆向掃描。先把所有障礙物放好,由後往前處理;遇到 type-1 就「移除」障礙物,
 *  這樣較早的查詢看到的就是正確的障礙物集合。移除會把兩個相鄰間隙合併成一個。
 *  兩棵線段樹:segGap 維護相鄰障礙物間的最大間隙,segPos 維護不超過 x 的最大障礙物座標。
 *  Offline + reverse scan. Place all obstacles, walk queries backwards, "remove" an
 *  obstacle on its type-1 query (merging two gaps). segGap tracks max gap between
 *  consecutive obstacles; segPos finds the largest obstacle position <= x.
 */

/* 給 qsort 用的整數比較器 / integer comparator for qsort */
static int cmpInt(const void *a, const void *b) {
    int x = *(const int *)a, y = *(const int *)b;  // 解參考取出兩個 int / dereference both ints
    return (x > y) - (x < y);                       // 回傳 -1/0/1,避免相減溢位 / safe compare, no overflow
}

/* 線段樹:點更新 + 區間最大值 / point-assign + range-max segment tree */
typedef struct {
    int *tree;  // 隱式二元樹陣列(1-indexed)/ implicit binary tree array (1-indexed)
    int n;      // 葉子數,代表座標 1..n / number of leaves = positions 1..n
} Seg;

/* 建立一棵全為 0 的線段樹 / create a segment tree filled with zeros */
static Seg *segCreate(int n) {
    Seg *s = (Seg *)malloc(sizeof(Seg));        // 配置結構本身 / allocate the struct
    s->n = n;                                   // 記住葉子數 / store leaf count
    s->tree = (int *)calloc(4 * (n + 1), sizeof(int)); // calloc 會把記憶體清成 0 / calloc zeroes memory; 4*(n+1) is safe size
    return s;
}

/* 把座標 pos 的值設成 val / set the value at position pos to val */
static void segUpdate(Seg *s, int node, int lo, int hi, int pos, int val) {
    if (lo == hi) {            // 走到葉子節點 / reached the leaf
        s->tree[node] = val;   // 直接賦值 / assign the new value
        return;
    }
    int mid = lo + (hi - lo) / 2;                       // 取中點,避免溢位 / midpoint without overflow
    if (pos <= mid) segUpdate(s, 2 * node, lo, mid, pos, val);          // 往左子樹 / go left
    else            segUpdate(s, 2 * node + 1, mid + 1, hi, pos, val);  // 往右子樹 / go right
    int a = s->tree[2 * node], b = s->tree[2 * node + 1];               // 取兩個子節點 / two children
    s->tree[node] = a > b ? a : b;                      // 父節點存兩者較大值 / parent keeps the max
}

/* 查詢區間 [ql, qr] 的最大值 / query the maximum over [ql, qr] */
static int segQuery(Seg *s, int node, int lo, int hi, int ql, int qr) {
    if (qr < lo || hi < ql) return 0;            // 完全沒交集,回傳 0(間隙與座標都 >=0)/ no overlap -> 0
    if (ql <= lo && hi <= qr) return s->tree[node]; // 區間被完全覆蓋,直接回傳 / fully covered
    int mid = lo + (hi - lo) / 2;                // 中點 / midpoint
    int a = segQuery(s, 2 * node, lo, mid, ql, qr);        // 查左半 / query left half
    int b = segQuery(s, 2 * node + 1, mid + 1, hi, ql, qr);// 查右半 / query right half
    return a > b ? a : b;                        // 合併取最大 / combine by max
}

bool *getResults(int **queries, int queriesSize, int *queriesColSize, int *returnSize) {
    /* 1) 掃一遍:找最大座標 maxX、數出障礙物個數 / first pass: find maxX and count obstacles */
    int maxX = 1, obsCount = 0;
    for (int i = 0; i < queriesSize; i++) {
        if (queries[i][1] > maxX) maxX = queries[i][1]; // queries[i][1] 是 x(兩種查詢都有)/ x exists in both types
        if (queries[i][0] == 1) obsCount++;             // type-1 才放障礙物 / only type-1 places obstacles
    }

    /* 2) 收集所有障礙物座標並排序 / collect obstacle positions and sort */
    int *obs = (int *)malloc(sizeof(int) * (obsCount > 0 ? obsCount : 1)); // 至少配 1 格避免 0 大小 / at least size 1
    int k = 0;
    for (int i = 0; i < queriesSize; i++)
        if (queries[i][0] == 1) obs[k++] = queries[i][1]; // 依出現順序先放進去 / gather then sort
    qsort(obs, obsCount, sizeof(int), cmpInt);            // 由小到大排序 / sort ascending

    /* 3) 座標 -> 排序後索引,方便移除時找鄰居 / map position to its sorted index */
    int *pos2idx = (int *)malloc(sizeof(int) * (maxX + 1));
    for (int i = 0; i <= maxX; i++) pos2idx[i] = -1;      // -1 表示該座標沒障礙物 / -1 means no obstacle
    for (int i = 0; i < obsCount; i++) pos2idx[obs[i]] = i;

    /* 4) 雙向鏈結串列:記住每個障礙物的左右鄰居 / doubly linked list of obstacles */
    int m = obsCount;
    int *prevArr = (int *)malloc(sizeof(int) * (m + 1));  // 多配一格給右哨兵 / extra slot for right sentinel
    int *nextArr = (int *)malloc(sizeof(int) * (m + 1));
    for (int i = 0; i < m; i++) { prevArr[i] = i - 1; nextArr[i] = i + 1; }
    /* prevArr[0] = -1 代表左邊沒有障礙物(視為座標 0)/ -1 = no left obstacle (acts as coord 0) */
    /* nextArr[m-1] = m 代表右邊沒有障礙物 / m = no right obstacle */

    /* 5) 建兩棵線段樹,並把「全部障礙物都在」的初始狀態填進去 / build trees for the all-present state */
    Seg *segGap = segCreate(maxX);  // 每個障礙物存它與左鄰的間隙 / gap to left neighbor
    Seg *segPos = segCreate(maxX);  // 每個障礙物存自己的座標 / its own position
    for (int i = 0; i < m; i++) {
        int gapLeft = obs[i] - (i > 0 ? obs[i - 1] : 0);  // 左鄰不存在就用 0 / left neighbor defaults to 0
        segUpdate(segGap, 1, 1, maxX, obs[i], gapLeft);
        segUpdate(segPos, 1, 1, maxX, obs[i], obs[i]);
    }

    /* 6) 算 type-2 數量、配置答案、預先決定每個查詢的輸出位置 / prepare output slots */
    int q2 = 0;
    for (int i = 0; i < queriesSize; i++) if (queries[i][0] == 2) q2++;
    bool *ans = (bool *)malloc(sizeof(bool) * (q2 > 0 ? q2 : 1));
    *returnSize = q2;                                     // 透過指標回傳答案長度 / report length via pointer
    int *outSlot = (int *)malloc(sizeof(int) * queriesSize);
    int s = 0;
    for (int i = 0; i < queriesSize; i++) outSlot[i] = (queries[i][0] == 2) ? s++ : -1; // 第幾個 type-2 / its rank

    /* 7) 逆向掃描所有查詢 / scan all queries in reverse */
    for (int i = queriesSize - 1; i >= 0; i--) {
        if (queries[i][0] == 1) {
            /* --- 移除障礙物(插入的逆操作)/ remove an obstacle (inverse of insert) --- */
            int x = queries[i][1];
            int idx = pos2idx[x];                 // 它在排序陣列中的索引 / its sorted index
            int l = prevArr[idx], r = nextArr[idx]; // 左右鄰居 / left & right neighbors
            segUpdate(segGap, 1, 1, maxX, x, 0);  // 它的間隙消失 / its gap disappears
            segUpdate(segPos, 1, 1, maxX, x, 0);  // 它不再是障礙物 / no longer an obstacle
            if (l >= 0) nextArr[l] = r;           // 重接鏈結串列 / relink left -> right
            if (r < m)  prevArr[r] = l;           // 重接鏈結串列 / relink right -> left
            if (r < m) {                          // 右鄰存在,它的間隙要擴大 / right neighbor's gap grows
                int prevPos = (l >= 0) ? obs[l] : 0; // 合併後右鄰的新左界 / merged gap's new left bound
                segUpdate(segGap, 1, 1, maxX, obs[r], obs[r] - prevPos);
            }
        } else {
            /* --- 回答 type-2 查詢 / answer a type-2 query --- */
            int x = queries[i][1], sz = queries[i][2];
            int lastObs = segQuery(segPos, 1, 1, maxX, 1, x); // 不超過 x 的最近障礙物(無則 0)/ nearest obstacle <= x
            int trailing = x - lastObs;            // 尾段空白:從該障礙物到 x / trailing free span
            int internalMax = segQuery(segGap, 1, 1, maxX, 1, x); // 內部最大間隙 / max internal gap with right end <= x
            int best = trailing > internalMax ? trailing : internalMax; // 兩種空白取較大 / largest empty span
            ans[outSlot[i]] = (best >= sz);        // 夠大就放得下 / fits iff best >= sz
        }
    }

    /* 8) 釋放暫存記憶體(ans 要回傳,不能 free)/ free scratch memory (keep ans) */
    free(obs); free(pos2idx); free(prevArr); free(nextArr); free(outSlot);
    free(segGap->tree); free(segGap);
    free(segPos->tree); free(segPos);
    return ans;
}

Solution — C++

#include <vector>
#include <algorithm>
using namespace std;

/*
 * 演算法 / Algorithm:
 *  離線 + 逆向掃描。先放好所有障礙物,由後往前處理,type-1 視為「移除」(合併兩個間隙)。
 *  segGap 維護相鄰障礙物間的最大間隙,segPos 找出不超過 x 的最大障礙物座標。
 *  Offline + reverse scan: place all obstacles, walk backwards, treat type-1 as removal
 *  (merging two gaps). segGap = max gap between consecutive obstacles; segPos = largest
 *  obstacle position <= x. Each type-2 query is two O(log) lookups.
 */
class Solution {
    // 線段樹:點更新 + 區間最大值 / point-assign + range-max segment tree
    struct Seg {
        int n;                  // 葉子數 / number of leaves
        vector<int> tree;       // 隱式樹,vector 會自動管理記憶體 / implicit tree; vector manages memory
        Seg(int n) : n(n), tree(4 * (n + 1), 0) {}  // 建構:開 4*(n+1) 個 0 / build with zeros

        // 內部遞迴版點更新 / internal recursive point update
        void update(int node, int lo, int hi, int pos, int val) {
            if (lo == hi) { tree[node] = val; return; }      // 葉子直接賦值 / assign at leaf
            int mid = lo + (hi - lo) / 2;                    // 中點 / midpoint
            if (pos <= mid) update(2 * node, lo, mid, pos, val);        // 往左 / go left
            else            update(2 * node + 1, mid + 1, hi, pos, val);// 往右 / go right
            tree[node] = max(tree[2 * node], tree[2 * node + 1]);       // 父=子的最大 / parent = max of children
        }
        void update(int pos, int val) { update(1, 1, n, pos, val); }    // 對外簡化介面 / public wrapper

        // 內部遞迴版區間最大查詢 / internal recursive range-max query
        int query(int node, int lo, int hi, int ql, int qr) {
            if (qr < lo || hi < ql) return 0;                // 無交集 / no overlap
            if (ql <= lo && hi <= qr) return tree[node];     // 完全覆蓋 / fully covered
            int mid = lo + (hi - lo) / 2;
            return max(query(2 * node, lo, mid, ql, qr),
                       query(2 * node + 1, mid + 1, hi, ql, qr)); // 左右取最大 / combine by max
        }
        int query(int ql, int qr) { return query(1, 1, n, ql, qr); }    // 對外簡化介面 / public wrapper
    };

public:
    vector<bool> getResults(vector<vector<int>>& queries) {
        int n = queries.size();
        int maxX = 1;
        vector<int> obs;                                  // 所有障礙物座標 / all obstacle positions
        for (auto& q : queries) {                          // range-for:逐一取出每個查詢 / iterate each query
            maxX = max(maxX, q[1]);                         // q[1] 是 x / x for both types
            if (q[0] == 1) obs.push_back(q[1]);             // type-1 收集障礙物 / collect obstacles
        }
        sort(obs.begin(), obs.end());                       // 由小到大排序 / sort ascending
        int m = obs.size();

        vector<int> pos2idx(maxX + 1, -1);                  // 座標 -> 排序索引,-1 表示無 / position -> sorted index
        for (int i = 0; i < m; i++) pos2idx[obs[i]] = i;

        vector<int> prevArr(m + 1), nextArr(m + 1);         // 雙向鏈結串列 / doubly linked list
        for (int i = 0; i < m; i++) { prevArr[i] = i - 1; nextArr[i] = i + 1; }
        // prevArr[0] = -1:左邊視為座標 0;nextArr[m-1] = m:右邊無障礙物
        // prevArr[0] = -1 acts as coord 0; nextArr[m-1] = m means no right obstacle

        Seg segGap(maxX), segPos(maxX);                     // 兩棵線段樹 / two segment trees
        for (int i = 0; i < m; i++) {
            int gapLeft = obs[i] - (i > 0 ? obs[i - 1] : 0); // 與左鄰的間隙,無左鄰用 0 / gap to left neighbor
            segGap.update(obs[i], gapLeft);
            segPos.update(obs[i], obs[i]);
        }

        vector<int> outSlot(n, -1);                          // 每個查詢的輸出位置 / output slot per query
        int q2 = 0;
        for (int i = 0; i < n; i++) if (queries[i][0] == 2) outSlot[i] = q2++;
        vector<bool> ans(q2);                                // 答案陣列 / answers

        for (int i = n - 1; i >= 0; i--) {                   // 逆向掃描 / reverse scan
            if (queries[i][0] == 1) {
                // --- 移除障礙物 / remove obstacle ---
                int x = queries[i][1], idx = pos2idx[x];
                int l = prevArr[idx], r = nextArr[idx];      // 左右鄰居 / neighbors
                segGap.update(x, 0);                         // 間隙消失 / gap gone
                segPos.update(x, 0);                         // 不再是障礙物 / not an obstacle
                if (l >= 0) nextArr[l] = r;                  // 重接 / relink
                if (r < m)  prevArr[r] = l;                  // 重接 / relink
                if (r < m) {                                 // 右鄰間隙擴大 / right neighbor's gap grows
                    int prevPos = (l >= 0) ? obs[l] : 0;     // 合併後的左界 / merged left bound
                    segGap.update(obs[r], obs[r] - prevPos);
                }
            } else {
                // --- 回答 type-2 / answer type-2 ---
                int x = queries[i][1], sz = queries[i][2];
                int lastObs = segPos.query(1, x);            // 不超過 x 的最近障礙物 / nearest obstacle <= x
                int trailing = x - lastObs;                  // 尾段空白 / trailing span
                int internalMax = segGap.query(1, x);        // 內部最大間隙 / max internal gap (right end <= x)
                ans[outSlot[i]] = max(trailing, internalMax) >= sz; // 取較大者與 sz 比 / fits iff best >= sz
            }
        }
        return ans;
    }
};

複雜度 / Complexity

  • Time: O((Q + V) log V)Q 是查詢數,V 是最大座標(maxX,最多 5 * 10^4)。建樹要對每個障礙物做一次 O(log V) 更新;之後每個查詢做常數次 O(log V) 的線段樹更新或查詢。排序障礙物是 O(m log m),被前者吸收。Q queries each cost O(log V) for the segment-tree operations; building the trees over the obstacles is O(m log V). The log factor is the height of the segment tree.
  • Space: O(V) — 兩棵線段樹各約 4V 個整數,加上 pos2idx、鏈結串列等也都是 O(V)O(Q)。Two segment trees of size ~4V, plus O(V)/O(Q) helper arrays.

Pitfalls & Edge Cases

  • 「碰到」不算相交 / touching is allowed:間隙 [a, b] 能容納長度 b - a(不是 b - a - 1)的方塊,因為端點可緊貼障礙物。判斷用 best >= sz 而非 >。Use >=, and the gap length is b - a.
  • 第一段與尾段空白別漏掉 / don't forget the first and trailing spans:從 0 到第一個障礙物(左鄰視為座標 0)以及從最後一個 <= x 的障礙物到 x 都是合法空白。程式分別用「左鄰預設 0」和 segPos 查詢來處理。Handle them via the "left neighbor defaults to 0" rule and the segPos lookup.
  • 正向插入難、逆向刪除易 / insert is hard, delete is easy:若正向處理,插入會把一個間隙拆成兩個,難維護最大值;逆向把它變成合併,鏈結串列 O(1) 搞定。這是本題用離線逆向的核心理由。This is the whole reason for going offline-and-reverse.
  • 線段樹陣列大小 / segment-tree array size:要開 4*(n+1),開太小會越界寫入造成難以察覺的錯誤。Under-sizing causes out-of-bounds writes.
  • maxX 要涵蓋所有 x / maxX must cover every x:type-2 的 x 也可能比任何障礙物座標大,必須一起納入 maxX,否則查詢 [1, x] 會越界。Include type-2 x values when computing maxX.
  • 沒有障礙物的情況 / no obstacles at all:若某查詢時 [0, x] 沒障礙物,segPos 查回 0trailing = x,整段空白可放 x,邏輯自然成立。Empty-range case falls out correctly: trailing = x.
  • 輸出順序 / output ordering:逆向處理時答案是亂序產生的,務必用預先算好的 outSlot 寫回正確位置,回傳的順序要對應 type-2 查詢的原始先後。Write answers back via outSlot, not in processing order.