1 条题解
-
0
思路与算法
把山顶的优先级定义为 的字典序,右侧同高山顶优先。先求每个位置能看到的最高点 ,包括当前位置本身。
对右侧而言,可见点恰为相对于当前位置的斜率严格创新高的点。只要候选点高于当前位置,斜率纪录越往后,高度也越高,所以只需最大仰角的最近切点;若右侧没有更高点,则只需考虑同高的最近可见点。扫描时维护上凸链,以叉积判断并弹出被后继遮住的点。共线时保留近点,因为它挡住远点。反向再扫一次得到左侧切点,取两侧切点和自身的最高优先级。
设 。从位置 出发,当前记忆目标是 。在从 到 的路上,用区间最大线段树寻找沿行走方向第一个满足 的位置 。此前每步方向不变;到 后,新目标超过原目标,状态就变成“从 出发”。若没有这样的点,就直接走到 。因此 ;没有中途更新时令 。全局最高点的答案是0,按 降序计算其余答案。
正确性说明
连接当前点与右侧某点的线段不碰到中间折线,当且仅当其斜率严格大于所有更近点的斜率。最大斜率若为正,后一个严格斜率纪录比前一个既远又有更大正斜率,因此高度更高;最大斜率共线时,最近点遮挡后点。最大斜率为0时最近同高点可见;为负时所有右侧点都低于自身,无须选。左侧同理。单调上凸链严格弹出被遮点、保留共线近点,故计算的 正确。
行走的历史只通过“截至目前见过的最高点”影响未来。在遇到第一个 之前,原目标始终不变,故恰沿指定方向到达 ;到达后记忆目标正是 ,其后过程与独立从 起步完全相同。若一直没有更高纪录,先到达原目标 ,随后从 更新观察并继续。
除全局最高点外,必存在比当前位置更优的可见点:通向更高点的最大仰角切点,或同高平台向右的最近点。于是 的优先级严格超过 。递推依赖的 严格超过 ,构成无环依赖;降序动态规划从终点出发正确计算全部步数。
复杂度
两次凸链扫描总计 ,每个点至多入栈、出栈各一次。建立优先级和依赖顺序各需 ,线段树初始化 。每个点至多一次首个或末个超阈查询,。总时间 ,空间 。叉积用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