1 条题解

  • 0
    @ 2026-10-27 8:00:08

    思路与算法

    把山顶的优先级定义为 (yi,i)(y_i,i) 的字典序,右侧同高山顶优先。先求每个位置能看到的最高点 f(i)f(i),包括当前位置本身。

    对右侧而言,可见点恰为相对于当前位置的斜率严格创新高的点。只要候选点高于当前位置,斜率纪录越往后,高度也越高,所以只需最大仰角的最近切点;若右侧没有更高点,则只需考虑同高的最近可见点。扫描时维护上凸链,以叉积判断并弹出被后继遮住的点。共线时保留近点,因为它挡住远点。反向再扫一次得到左侧切点,取两侧切点和自身的最高优先级。

    设 vi=rank⁡(f(i))v_i=\operatorname{rank}(f(i))。从位置 ii 出发,当前记忆目标是 t=f(i)t=f(i)。在从 ii 到 tt 的路上,用区间最大线段树寻找沿行走方向第一个满足 vj>viv_j>v_i 的位置 jj。此前每步方向不变;到 jj 后,新目标超过原目标,状态就变成“从 jj 出发”。若没有这样的点,就直接走到 tt。因此 Di=∣i−j∣+DjD_i=|i-j|+D_j;没有中途更新时令 j=tj=t。全局最高点的答案是0,按 viv_i 降序计算其余答案。

    正确性说明

    连接当前点与右侧某点的线段不碰到中间折线,当且仅当其斜率严格大于所有更近点的斜率。最大斜率若为正,后一个严格斜率纪录比前一个既远又有更大正斜率,因此高度更高;最大斜率共线时,最近点遮挡后点。最大斜率为0时最近同高点可见;为负时所有右侧点都低于自身,无须选。左侧同理。单调上凸链严格弹出被遮点、保留共线近点,故计算的 f(i)f(i) 正确。

    行走的历史只通过“截至目前见过的最高点”影响未来。在遇到第一个 vj>viv_j>v_i 之前,原目标始终不变,故恰沿指定方向到达 jj;到达后记忆目标正是 f(j)f(j),其后过程与独立从 jj 起步完全相同。若一直没有更高纪录,先到达原目标 tt,随后从 tt 更新观察并继续。

    除全局最高点外,必存在比当前位置更优的可见点:通向更高点的最大仰角切点,或同高平台向右的最近点。于是 f(t)f(t) 的优先级严格超过 tt。递推依赖的 vjv_j 严格超过 viv_i,构成无环依赖;降序动态规划从终点出发正确计算全部步数。

    复杂度

    两次凸链扫描总计 O(n)O(n),每个点至多入栈、出栈各一次。建立优先级和依赖顺序各需 O(nlog⁡n)O(n\log n),线段树初始化 O(n)O(n)。每个点至多一次首个或末个超阈查询,O(log⁡n)O(\log n)。总时间 O(nlog⁡n)O(n\log n),空间 O(n)O(n)。叉积用64位整数;行走步数与答案用64位整数。

    易错点

    看见的最高点和最高仰角不是同一概念,只有结合候选高度相对自身的符号才能用切点。共线中间点会遮挡远点,不能把共线弹栈条件写成大于等于。同高时以横坐标较大者为最高,必须纳入优先级。向左行走须找区间最右超阈点,向右须找最左点。目标记忆不能每走一步无条件重置。

    参考代码(C++11)

    #include <bits/stdc++.h>
    using namespace std;
    using ll = long long;
    struct Tree {
        int n;
        vector<int> a;
        Tree(const vector<int> &v) {
            n = 1;
            while (n < (int)v.size())
                n *= 2;
            a.assign(n * 2, -1);
            for (int i = 0; i < (int)v.size(); ++i)
                a[n + i] = v[i];
            for (int i = n - 1; i; --i)
                a[i] = max(a[i * 2], a[i * 2 + 1]);
        }
        int find(int u, int l, int r, int ql, int qr, int h, bool rev) {
            if (r < ql || qr < l || a[u] <= h)
                return -1;
            if (l == r)
                return l;
            int mid = (l + r) / 2, x;
            if (rev) {
                x = find(u * 2 + 1, mid + 1, r, ql, qr, h, rev);
                if (x < 0)
                    x = find(u * 2, l, mid, ql, qr, h, rev);
            } else {
                x = find(u * 2, l, mid, ql, qr, h, rev);
                if (x < 0)
                    x = find(u * 2 + 1, mid + 1, r, ql, qr, h, rev);
            }
            return x;
        }
        int find(int l, int r, int h, bool rev) {
            return find(1, 0, n - 1, l, r, h, rev);
        }
    };
    int main() {
        ios::sync_with_stdio(false);
        cin.tie(nullptr);
        int n;
        if (!(cin >> n))
            return 0;
        vector<ll> x(n), y(n), ans(n);
        for (int i = 0; i < n; ++i)
            cin >> x[i] >> y[i];
        vector<int> f(n), rank(n), order(n);
        iota(f.begin(), f.end(), 0);
        iota(order.begin(), order.end(), 0);
        auto better = [&](int a, int b) {
            return y[a] != y[b] ? y[a] > y[b] : a > b;
        };
        sort(order.begin(), order.end(), better);
        for (int j = 0; j < n; ++j)
            rank[order[j]] = n - j;
        for (int dir : {1, -1}) {
            vector<int> st;
            for (int step = 0; step < n; ++step) {
                int i = dir == 1 ? n - 1 - step : step;
                auto cross = [&](int b, int c) {
                    return dir * ((x[b] - x[i]) * (y[c] - y[i]) - (y[b] - y[i]) * (x[c] - x[i]));
                };
                while (st.size() > 1 && cross(st.back(), st[st.size() - 2]) > 0)
                    st.pop_back();
                if (!st.empty() && better(st.back(), f[i]))
                    f[i] = st.back();
                st.push_back(i);
            }
        }
        vector<int> v(n);
        for (int i = 0; i < n; ++i)
            v[i] = rank[f[i]];
        Tree seg(v);
        sort(order.begin(), order.end(), [&](int a, int b) {
            return v[a] > v[b];
        });
        for (int i : order) {
            int t = f[i];
            if (t == i) {
                ans[i] = 0;
                continue;
            }
            int j = seg.find(min(i, t), max(i, t), v[i], t < i);
            if (j < 0)
                j = t;
            ans[i] = abs(i - j) + ans[j];
        }
        for (ll z : ans)
            cout << z << '\n';
    }
    
    • 1