题意:有一排建筑,每座建筑有一定的高度,宽度可以忽略,求在某点的平地上能看到天空的最大角度。
网上的做法基本都是离线的...其实这道题是可以在线做的。
对于向右能看到的最大角度,从右往左倍增维护每个时刻的单调栈(凸壳),对于每个询问,先二分找到它右面的第一个建筑的位置,然后在对应的凸壳上倍增找到切点即可。
向左看把x坐标对称一下就行。
复杂度$O(nlogn)$
1 #include<bits/stdc++.h> 2 using namespace std; 3 typedef long long ll; 4 typedef double db; 5 const db pi=acos(-1); 6 const int N=1e5+10,inf=0x3f3f3f3f; 7 int n,m,ka; 8 struct P { 9 db x,y; 10 P operator-(const P& b) {return {x-b.x,y-b.y};} 11 db rad() {return atan2(y,x);} 12 bool operator<(const P& b)const {return x<b.x;} 13 } a[N]; 14 struct D { 15 P p,v; 16 bool operator<(const D& b)const {return p<b.p;} 17 }; 18 db cross(const P& a,const P& b) {return a.x*b.y-a.y*b.x;} 19 struct Solver { 20 int F[N][20]; 21 D b[N]; 22 void build(P* a) { 23 b[n]= {a[n],{0,-1}}; 24 for(int i=1; i<n; ++i)b[i]= {a[i],a[i+1]-a[i]}; 25 for(int k=19; k>=0; --k)F[n][k]=n; 26 for(int i=n-1; i>=1; --i) { 27 int j=i+1; 28 if(cross(b[j].p-a[i],b[j].v)>0) { 29 for(int k=19; k>=0; --k) 30 if(cross(b[F[j][k]].p-a[i],b[F[j][k]].v)>0)j=F[j][k]; 31 j=F[j][0]; 32 } 33 F[i][0]=j; 34 b[i].v=a[j]-a[i]; 35 for(int k=1; k<20; ++k)F[i][k]=F[F[i][k-1]][k-1]; 36 } 37 } 38 db qry(P px) { 39 int i=lower_bound(b+1,b+1+n,(D) {px, {0,0}})-b; 40 if(cross(b[i].p-px,b[i].v)>0) { 41 for(int k=19; k>=0; --k) 42 if(cross(b[F[i][k]].p-px,b[F[i][k]].v)>0)i=F[i][k]; 43 i=F[i][0]; 44 } 45 return (b[i].p-px).rad()*180/pi; 46 } 47 } solver[2]; 48 int main() { 49 int T; 50 for(scanf("%d",&T); T--;) { 51 printf("Case #%d: ",++ka); 52 scanf("%d",&n); 53 for(int i=1; i<=n; ++i)scanf("%lf%lf",&a[i].x,&a[i].y); 54 a[++n]= {~inf,0},a[++n]= {inf,0}; 55 sort(a+1,a+1+n); 56 solver[0].build(a); 57 for(int i=1; i<=n; ++i)a[i]= {-a[i].x,a[i].y}; 58 reverse(a+1,a+1+n); 59 solver[1].build(a); 60 for(scanf("%d",&m); m--;) { 61 db x; 62 scanf("%lf",&x); 63 printf("%.10f ",180-(solver[0].qry({x,0})+solver[1].qry({-x,0}))); 64 } 65 } 66 return 0; 67 }