hdu5033 Building (单调栈维护凸包)

hdu5033 Building (单调栈维护凸包)

Problem Description
Once upon a time Matt went to a small town. The town was so small and narrow that he can regard the town as a pivot. There were some skyscrapers in the town, each located at position xi with its height hi. All skyscrapers located in different place. The skyscrapers had no width, to make it simple. As the skyscrapers were so high, Matt could hardly see the sky.Given the position Matt was at, he wanted to know how large the angle range was where he could see the sky. Assume that Matt’s height is 0. It’s guaranteed that for each query, there is at least one building on both Matt’s left and right, and no building locate at his position.
Input
The first line of the input contains an integer T, denoting the number of testcases. Then T test cases follow.
Each test case begins with a number N(1<=N<=10^5), the number of buildings.
In the following N lines, each line contains two numbers, xi(1<=xi<=10^7) and hi(1<=hi<=10^7).
After that, there’s a number Q(1<=Q<=10^5) for the number of queries.
In the following Q lines, each line contains one number qi, which is the position Matt was at.
Output
For each test case, first output one line “Case #x:”, where x is the case number (starting from 1).Then for each query, you should output the angle range Matt could see the sky in degrees. The relative error of the answer should be no more than 10^(-4).
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

感觉到单调栈如此强大,和遍历o(n^2)的不同应该就是途中就会删去没有价值的元素。

题意: 一个人在x轴上,他的左右两侧都有高楼,给出楼的横坐标Xi和高度Hi还有人的位置pos,求人所能看到的天空的最大角度。
题解
维护 相邻两建筑顶(xi,hi)的连线的斜率的绝对值上升 的单调栈。
先把建筑和queries的点全部弄到一起,按xi排个序。然后从左到右来一波得出在某个空地往左看看到最高的是哪个建筑,再反过来来一波。
先按从左到右的情况来说:
维护单调栈,栈里存的是之后的空地可能看到的建筑,容易知这是递减的单调栈。
再思考,如果:
在这里插入图片描述
则只用存两边的点,中间那3个肯定看不到了。
如果:
在这里插入图片描述
则都要存,因为往右走的时候走着走着,右边第二个就比右边第一个高了,走着走着右边第三个又比右边第二个高了……(这时pop掉栈顶
可见我们存的是相邻两建筑顶(xi,hi)的连线的斜率的绝对值上升 的单调栈。
每看到一个空地,把栈首的不够高的都pop到,只留下那个能看到的最高的,然后把这个建筑加入结果记录中。(记录从这个空地往左看看到的最高的是哪个建筑)
反过来再来一遍。
最后再对询问搞一搞,就完啦。

已向左看为例,排序后。分两种情况。
1.此点为楼,需要先pop 比此楼低的元素和大于等于此楼斜率的点,因为需要维护一个h单调递减并且斜率单调递增的元素。pop对之后的人肯定没有任何影响,仔细想一下 。之后push该元素,注意当栈为空时,斜率应设为0.
2.此点为人,需要找到他最先看到的凸点,即人与楼的斜率大于楼的斜率时,该点为凸点。pop之间无用的元素。(如果向左看的第一个楼不是凸点,肯定需要pop的),pop对之后的人肯定没有任何影响。计算答案,注意人的左侧可能没有楼的情况。

注意
1.此题会卡精度,需要模拟除法,最开始存每个点的斜率比较时,会损失。
2.输入为实数,而不是整型。
3.数组,栈的清空。
4.注意栈中无元素时,入栈时需将斜率设置为0。
5.注意栈中无元素时,人的ans不需要记录。
6.正常来说需要用fabs,可是abs也能过。。。why~~

下面附上萌萌哒的代码~~~

#include<bits/stdc++.h>
#define mem(a,x) memset(a,x,sizeof(a))
#define ios ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
typedef long long ll;
typedef unsigned long long ull; // %llu
const double PI = acos(-1.0);
const double eps = 1e-6;
const int mod=1e9+7;
const int INF = -1u>>1;
const int maxn = 1e6+5;
struct node
{
    double x,y,valy,valx; //valy/valx为斜率
    int num;
} p[maxn];
bool cmp(node a,node b)
{
    return a.x<b.x;
}
int n,m,Time;
double ans[maxn];
stack<node>st;
void init() //输入
{
    mem(ans,0);
    while(!st.empty())
        st.pop();
    scanf("%d",&n);
    for(int i=1; i<=n; i++)
    {
        cin>>p[i].x>>p[i].y;
        p[i].num=-1;
    }
    scanf("%d",&m);
    for(int i=n+1; i<=m+n; i++)
    {
        cin>>p[i].x;
        p[i].num=i-n;
    }
    sort(p+1,p+n+m+1,cmp);
}
void solve(int i)
{
    if(p[i].num==-1)
    {
        while(!st.empty()&&!(st.top().y>p[i].y&&st.top().valy*fabs(st.top().x-p[i].x)<fabs(st.top().y-p[i].y)*st.top().valx))       //维护单调减且斜率增的单调栈
        {
            st.pop();
        }
        if(!st.empty())             
            st.push(node{p[i].x,p[i].y,fabs(st.top().y-p[i].y),fabs(st.top().x-p[i].x)});           //入栈
        else
            st.push(node{p[i].x,p[i].y,0,1});           //栈内无元素时,将斜率设为0
    }
    else
    {
        while(st.size()>=2&&st.top().valy*fabs(p[i].x-st.top().x)>=st.top().y*st.top().valx)    //删除不是凸点的元素
        {
            st.pop();
        }
        if(!st.empty())
            ans[p[i].num]+=atan(st.top().y*1.0/fabs(p[i].x-st.top().x))/PI*180.0;   //当栈内有元素时才可记录答案
    }
}
void outit()    //输出
{
    printf("Case #%d:\n",++Time);
    for(int i=1; i<=m; i++)
    {
        printf("%.10lf\n",180-ans[i]);
    }
}
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        init();
        for(int i=1; i<=n+m; i++)
        {
            solve(i);
        }
        while(!st.empty())  //清空元素后倒着在枚举一遍
            st.pop();
        for(int i=n+m; i>=1; i--)
        {
            solve(i);
        }
        outit();
    }
}

/*
10
2
1 0
3 0
1
2

3
1 0
3 0
4 0.00000001
1
2

3
5 1
1 4
2 3
1
4

3
3 2
1 5 
1 1
2
1
4

Case #1:
180.0000000000
Case #2:
179.9999997135
Case #3:
78.6900675260
Case #4:
45.0000000000
116.5650511771

*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值