题解:P15589 [KTSC 2026] 瞭望塔 / Observation Tower

· · 题解

题目传送门

P15589 [KTSC 2026] 瞭望塔 / Observation Tower

题意

每一个塔它都有一个高度,和一个分数。 我们定义一个严格上升的子序列成为一个塔能被另一个塔瞭望到。 题目给给定了三个操作:

思路讲解

看见这样的区间操作,我们的第一反应就是线段树。

既然我们考虑到了线段树,就要考虑我们要用线段树维护哪些信息。我们首先可以知道要维护一个区间加的懒标记和区间和,因为要求任意区间的相对于一个数的严格上升的最长子序列,自然就要维护一个最大值。我们定义一个 cmx 表示在当前这个节点下,在左端点的左边界不受到任何限制的情况下,即这个区间的最多的能被瞭望到的塔的数目,还有维护对于可被瞭望的点进行延迟处理的懒标记。

设想一个函数 pushtag:

它的含义是:在左边界外部有一个最大高度为 maxn,现在我们要给当前区间内所有“大于 maxn 且能突破遮蔽”的点,把 S 加上 v。

Coding

#include <bits/stdc++.h>
using std::cin; using std::cout;
using std::min; using std::max;
using std::vector;

#define LL long long

const int N = 1e6 + 10;

// 线段树节点
struct node {
    LL sum;      // S 的区间和
    LL mx;       // H 的区间最大值
    int cmx;     // 内部可见链长度(严格后缀最大值个数)
    LL add_tag;  // H 的加法标记
    LL cmx_tag;  // 可见点 S 的加法标记
} static tree[N << 2];

// 给节点 id 的高度整体加上 w
static inline void pushadd(int id, LL w) {
    tree[id].mx += w;         // 最大值整体加 w
    tree[id].add_tag += w;    // 累积懒标记
}

// 在外部遮挡限制为 maxn 时,区间内还能被看到的点数
static inline int calc(int id, int L, int R, LL maxn) {
    if (tree[id].mx <= maxn) return 0;  // 全被挡住
    if (L == R) return 1;               // 叶子且未被挡,可见
    int lc = id << 1, rc = lc | 1;
    if (tree[id].add_tag != 0) {        // 下放高度标记保证 mx 真实
        LL t = tree[id].add_tag;
        pushadd(lc, t);
        pushadd(rc, t);
        tree[id].add_tag = 0;
    }
    int mid = (L + R) >> 1;
    if (tree[lc].mx <= maxn)            // 左子全被挡,只看右边
        return calc(rc, mid + 1, R, maxn);
    else                                 // 左子部分可见
        return calc(lc, L, mid, maxn) + (tree[id].cmx - tree[lc].cmx);
}

// 由左右子树合并当前节点信息
static inline void push_up(int id, int L, int R) {
    int mid = (L + R) >> 1;
    int lc = id << 1, rc = lc | 1;
    LL mxl = tree[lc].mx, mxr = tree[rc].mx;
    tree[id].mx = mxl > mxr ? mxl : mxr;             // 取最大高度
    tree[id].sum = tree[lc].sum + tree[rc].sum;      // 合并 S 之和
    if (mxr <= mxl) {
        tree[id].cmx = tree[lc].cmx;                 // 右边全被左边挡住
    } else {
        tree[id].cmx = tree[lc].cmx + calc(rc, mid + 1, R, mxl);  // 右边有突破
    }
}

// 建树
static inline void build_tree(int id, int L, int R, const vector<int> &H) {
    if (L == R) {
        tree[id].sum = 0;        // 初始 S 为 0
        tree[id].mx = H[L];      // 叶子高度
        tree[id].cmx = 1;        // 叶子单独可见
        return ;
    }
    int mid = (L + R) >> 1;
    build_tree(id << 1, L, mid, H);          // 建左子树
    build_tree(id << 1 | 1, mid + 1, R, H);  // 建右子树
    push_up(id, L, R);                       // 合并
}

// 在遮挡限制 maxn 下,把可见点的 S 加 v
static inline void push_tag(int id, int L, int R, LL maxn, LL v) {
    if (tree[id].mx <= maxn) return ;  // 情况 A:全被挡
    if (L == R) {                      // 叶子可见,直接更新
        tree[id].sum += v * tree[id].cmx;
        tree[id].cmx_tag += v;
        return ;
    }
    int lc = id << 1, rc = lc | 1;
    if (tree[id].add_tag != 0) {       // 下放高度标记
        LL t = tree[id].add_tag;
        pushadd(lc, t);
        pushadd(rc, t);
        tree[id].add_tag = 0;
    }
    int mid = (L + R) >> 1;
    if (tree[lc].mx <= maxn) {         // 情况 B:左子全被挡
        LL old = tree[rc].sum;
        push_tag(rc, mid + 1, R, maxn, v);
        tree[id].sum += tree[rc].sum - old;   // 用差值更新 sum
    } else {                            // 情况 C:左子部分可见
        tree[id].cmx_tag += v;                        // 右子突破部分整体加
        tree[id].sum += v * (tree[id].cmx - tree[lc].cmx);  // O(1) 更新
        LL old = tree[lc].sum;
        push_tag(lc, L, mid, maxn, v);                // 递归左子
        tree[id].sum += tree[lc].sum - old;           // 用差值更新 sum
    }
}

// 下放懒标记
static inline void push_down(int id, int L, int R) {
    int mid = (L + R) >> 1;
    int lc = id << 1, rc = lc | 1;
    if (tree[id].add_tag != 0) {      // 高度标记直接下放
        LL t = tree[id].add_tag;
        pushadd(lc, t);
        pushadd(rc, t);
        tree[id].add_tag = 0;
    }
    if (tree[id].cmx_tag != 0) {      // S 标记只传给右子树
        LL t = tree[id].cmx_tag;
        push_tag(rc, mid + 1, R, tree[lc].mx, t);  // 右子受左子最大高度限制
        tree[id].cmx_tag = 0;
    }
}

// 区间加高度
static inline void add_h(int id, int L, int R, int l, int r, LL v) {
    if (r < L || R < l) return ;               // 无交集
    if (l <= L && R <= r) {                    // 完全覆盖
        pushadd(id, v);                        // 直接打高度标记
        return ;
    }
    push_down(id, L, R);                       // 下放
    int mid = (L + R) >> 1;
    add_h(id << 1, L, mid, l, r, v);           // 左
    add_h(id << 1 | 1, mid + 1, R, l, r, v);   // 右
    push_up(id, L, R);                         // 合并
}

// 处理瞭望操作,mx 随区间最大值动态更新
static inline void add_op(int id, int L, int R, int l, int r, LL &mx) {
    if (r < L || R < l) return ;               // 无交集
    if (l <= L && R <= r) {                    // 完全覆盖
        push_tag(id, L, R, mx, 1);             // 遮挡限制 mx,S 加 1
        if (tree[id].mx > mx) mx = tree[id].mx;  // 更新遮挡限制
        return ;
    }
    push_down(id, L, R);
    int mid = (L + R) >> 1;
    add_op(id << 1, L, mid, l, r, mx);         // 先左后右保证链式更新
    add_op(id << 1 | 1, mid + 1, R, l, r, mx);
    push_up(id, L, R);
}

// 区间求和 S
static inline LL ask(int id, int L, int R, int l, int r) {
    if (r < L || R < l) return 0;              // 无交集
    if (l <= L && R <= r) return tree[id].sum; // 完全覆盖直接返回
    push_down(id, L, R);                       // 下放
    int mid = (L + R) >> 1;
    return ask(id << 1, L, mid, l, r) + ask(id << 1 | 1, mid + 1, R, l, r);
}

// 单点查高度
static inline LL get(int id, int L, int R, int x) {
    if (L == R) return tree[id].mx;            // 叶子直接返回
    push_down(id, L, R);                       // 下放后才能得到真实值
    int mid = (L + R) >> 1;
    if (x <= mid) return get(id << 1, L, mid, x);
    else return get(id << 1 | 1, mid + 1, R, x);
}

// 交互主函数
vector<long long> tower_events(vector<int> H, vector<vector<int>> E) {
    int n = (int)H.size();
    if (n > 0) build_tree(1, 0, n - 1, H);     // 建树
    vector<long long> ans;

    for (const auto &e : E) {
        int siz = (int)e.size();
        if (siz == 1) {                        // 瞭望事件
            int I = e[0];
            LL mx = get(1, 0, n - 1, I);       // 先取塔 I 的高度
            if (I + 1 <= n - 1)
                add_op(1, 0, n - 1, I + 1, n - 1, mx);  // 更新右侧可见点
        } else if (siz == 2) {                 // 测量事件
            ans.push_back(ask(1, 0, n - 1, e[0], e[1]));  // 区间求和
        } else if (e[2] != 0) {                // 平移事件,V=0 跳过
            add_h(1, 0, n - 1, e[0], e[1], e[2]);         // 区间加高度
        }
    }
    return ans;
}