题意:
n个人,n栋楼,问每个人往左看,往右看,能够看到的天空的最大角度。
【这是一个很经典的单调栈问题】
思路:
首先,能够想到将人和楼全部读入栈中进行统一处理。并且左边跑一次单调栈,求出人能够往左看到的最高的楼。右边跑一次,处理出人能够往右看到的最高的楼房。【reverse(a+1,a+1+num)即可换方向再跑一遍】
问题在于如何处理出每个人能够看到的最高楼房。
容易想到,栈内元素的楼高一定是依次递减的,维护递减序列。
并且维护栈内相邻元素斜率递增,这样才能保证对于栈内第i个点,它能够看到的最高的楼房就是i-1所代表的楼房。
这样按照两个递减方案维护出的单调栈即可求出所有节点能够看到的最远的楼房。
注意:
atan()函数为反三角函数,求出的值是弧度值,需要*180/pai转换为角度值。const double pai = 3.141592654。
总结:
总结一下单调栈和单调队列。
单调队列:主要应用k区间问题,即滑动窗口,求出k区间内的最大值、最小值、sum和。
单调栈:维护栈内单调性,求出每个节点往右或往左的最大值、最小值(可以根据自己定义的出入栈规则维护题目所需的最大值、最小值)。
代码:
#include <cstdio>
#include <iostream>
#include <cstring>
#include <map>
#include <cmath>
#include <algorithm>
#define rep(i,a,b) for(int i = a; i <= b; i++)
using namespace std;
const int N = 1e5+100;
const double pai = 3.141592654;
int n,m,num,cnt;
double query[N];
pair<double,double> a[2*N];
map<double,double> mp;
int stack[2*N];
void solve()
{
stack[0] = 0;
int tp = 0;
rep(i,1,num)
{
while(tp > 0 && a[i].second > a[stack[tp]].second)
tp--;
while(tp > 1)
{
double t1 = fabs(a[i].second-a[stack[tp]].second)/fabs(a[i].first-a[stack[tp]].first);
double t2 = fabs(a[stack[tp]].second-a[stack[tp-1]].second)/fabs(a[stack[tp-1]].first-a[stack[tp]].first);
// printf("t1: %f, t2:%f\n",t1,t2);
if(t1 >= t2) break;
else{
tp--;
}
}
stack[++tp] = i;
if(a[stack[tp]].second == 0)
{
// printf("self: %f, building: %f\n",a[stack[tp]].first,a[stack[tp-1]].first);
double ang = fabs(a[stack[tp-1]].second-a[stack[tp]].second)/fabs(a[stack[tp-1]].first-a[stack[tp]].first);
mp[a[stack[tp]].first] += atan(ang)*180.0/pai;
// printf("ang: %f,add: %f\n",ang,mp[a[stack[tp]].first]);
}
}
}
int main()
{
int T;
scanf("%d",&T);
rep(kk,1,T)
{
a[0] = make_pair(0,0);
mp.clear();
num = cnt = 0;
scanf("%d",&n);
rep(i,1,n){
int x,y;
scanf("%d%d",&x,&y);
a[++num] = make_pair(x,y);
}
scanf("%d",&m);
rep(i,1,m){
int x;
scanf("%d",&x);
query[++cnt] = x;
a[++num] = make_pair(x,0);
}
sort(a+1,a+1+num);
printf("Case #%d:\n",kk);
solve();
reverse(a+1,a+1+num);
solve();
rep(i,1,cnt){
printf("%f\n",180.0-mp[query[i]]);
}
}
return 0;
}
/*
对于每一个点,只维护这个点能够向左看到的最高楼,中间的所有楼都去掉
对于x1 < x2,x1这个点能看到的最高楼,如果x1和x2中间没有楼,x2一定可以看到
*/