@desription@
一条数轴上有 N 个高楼,给定每栋楼的坐标和高度,保证两两之间坐标不相等。
多次询问。每次询问如果在点 (qi, 0) 进行观测,有多大的角度范围可以观测到天空。保证询问的坐标上没有高楼且左右都至少有一栋楼。
input
多组数据,第一行给出数据组数 T。
接下来的每组数据,第一行先给出 N,表示高楼数量,1 <= N <= 10^5。
接下来 N 行,每行两个数字 xi, hi,表示高楼的坐标与高度,1 <= xi, hi <= 10^7。
接下来一行给出 Q,表示询问次数,1 <= Q <= 10^5。
接下来 Q 行,每行给出 qi,表示询问的坐标为 (qi, 0)。
output
对于第 x 组数据,先输出 “Case #x:”。
然后对于每一个询问,输出相应的角度范围。
sample input
3
3
1 2
2 1
5 1
1
4
3
1 3
2 2
5 1
1
4
3
1 4
2 3
5 1
1
4
sample output
Case #1:
101.3099324740
Case #2:
90.0000000000
Case #3:
78.6900675260
@solution@
以 x = qi 这一条直线为分界,分为左右两部分统计角度范围。接下来我们只考虑右半部分,左半部分同理。
假如 (qi, 0) 右边有一个楼 (xj, hj),则从 (qi, 0) 向右观测,能够仰望到楼顶的角度 (arctan(frac{h_j}{q_j-x_i}))。如果要仰望到天空,则角度必须要大于等于这个值。那我们就是要求上面的最大值。
又因为 (arctan) 是单调的,我们即是要求解 (frac{h_j}{q_j-x_i}) 的最大值。
考虑这个式子的几何意义:过点 (qi, 0) 与 (xj, hj) 的直线的斜率。
根据几何直观,我们需要维护一个上凸包,才能求到斜率的最大值。
又根据几何直观,当观测点左移时,使斜率取到最大值的高楼是单调的。
所以我们就可以用单调队列维护凸包了。
一开始把询问存下来,然后询问坐标和高楼坐标一起排个序,然后从左往右再从右往左做两次,凸包维护一下,查询一下即可。
@accepted code@
#include<cmath>
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 100000;
const double PI = acos(-1);
struct node{
int type, n;
double x, h;
node(int _t=0, int _n=0, double _x=0, double _h=0):type(_t), n(_n), x(_x), h(_h){}
}a[2*MAXN + 5];
bool operator < (node a, node b) {
return a.x < b.x;
}
double slope(int p, int q) {
return (a[p].h - a[q].h) / (a[p].x - a[q].x);
}
double ans[MAXN + 5];
int stk[MAXN + 5], top;
void solve() {
int N, Q;
scanf("%d", &N);
for(int i=1;i<=N;i++) {
double x, h;
scanf("%lf%lf", &x, &h);
a[i] = node(0, 0, x, h);
}
scanf("%d", &Q);
for(int i=1;i<=Q;i++) {
double q;
scanf("%lf", &q);
a[i + N] = node(1, i, q, 0);
}
sort(a+1, a+N+Q+1); top = 0;
for(int i=1;i<=N+Q;i++) {
while( top > 1 && slope(stk[top-1], stk[top]) <= slope(stk[top], i) )
top--;
if( a[i].type )
ans[a[i].n] += atan(fabs(slope(stk[top], i)));
else stk[++top] = i;
}
top = 0;
for(int i=N+Q;i>=1;i--) {
while( top > 1 && slope(stk[top], stk[top-1]) >= slope(stk[top], i) )
top--;
if( a[i].type )
ans[a[i].n] += atan(fabs(slope(stk[top], i)));
else stk[++top] = i;
}
for(int i=1;i<=Q;i++) {
printf("%.10f
", 180 - ans[i]/PI*180);
ans[i] = 0;
}
}
int main() {
int T; scanf("%d", &T);
for(int i=1;i<=T;i++) {
printf("Case #%d:
", i);
solve();
}
}
@details@
从左往右加入点时,横坐标是单增的。
但是从右往左加入点时,横坐标是单减的。
所以两个代码不能直接复制粘贴 qwq。