HDU 5033 Building(单调栈, 类凸包)

Building

Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Others)
Total Submission(s): 1661    Accepted Submission(s): 488
Special Judge


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 x i with its height h i. 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, x i(1<=x i<=10^7) and h i(1<=h i<=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 q i, 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
 

Source
 


题目大意:

地面上有n座楼,分别站在m个位置上,问每个位置能看见多大角度的天空。


解题思路:

顶层模型:凸包应用。

总体思路:排序后,从左到右再从右到左,分两次得出每个查询点的左侧和右侧天空角度,相加即是答案。

详细分析:

通过画图分析,可以得出以下两点:

1. 如上图所示,在一座高楼左侧的矮楼,对于高楼右侧的任意位置,计算天空角度时都是无关的。

2. 如上图所示,中间的紫色楼,对于第四座楼右侧的位置,计算角度时也是无关的。右侧位置的左仰角都在红线和黄线之间。


所以,这题可以用类似求凸包的方法,利用单调栈进行维护。计算左侧天空角度时,左左向右扫描,始终维护当前站立位置左侧得上凸包,因为不在凸包上的点(将楼房抽象为顶部代表的点),对于当前及之后的角度计算都是无关的,可以直接去掉。

对于一个位置,其左侧天空角度,就是沿着下凸包走(即维护上凸包的过程),停止时其与栈首的第一座房子的夹角。


以上分析和解题过程,对右侧情况同样适用,只需对坐标反转即可。两侧角度相加就是整个天空的角度。

解题技巧:扫描时,可以将楼房和询问位置统一处理,减少代码量。


参考代码:

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
using namespace std;

const int MAXN = 200010;
const double PI = acos(-1);
double ans[MAXN >> 1];
int nCase, cCase, n, q;

struct Building {
    int x, h, id;
    bool operator < (const Building &t) const {
        return x < t.x;
    }
} building[MAXN], st[MAXN];

void input() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d%d", &building[i].x, &building[i].h);
    }
    scanf("%d", &q);
    for (int i = 0; i < q; i++) {
        scanf("%d", &building[n+i].x);
        building[n+i].id = i;
        building[n+i].h = 0;
        ans[i] = 0;
    }
}

double angle(const Building &a, const Building &b) {
    return atan((double)a.h / (double)(b.x - a.x));
}

bool judge(const Building &a, const Building &b, const Building &c) {
    return (long long)(c.x - a.x) * (c.h - b.h) - (long long)(c.x - b.x) * (c.h - a.h) >= 0;
}

void calc() {
    int head = 0;
    for (int i = 0; i < n+q; i++) {
        if (building[i].h > 0) {  // building
            //while (head-1 >= 0 && st[head-1].h <= building[i].h) head--;
            while (head-2 >= 0 && judge(st[head-2], st[head-1], building[i])) head--;
            st[head++] = building[i];
        } else {  // position
            while (head-2 >= 0 && judge(st[head-2], st[head-1], building[i])) head--;
            ans[building[i].id] += angle(st[head-1], building[i]);
        }
    }
}

void solve() {
    // left
    sort(building, building+n+q);
    calc();
    // right
    reverse(building, building+n+q);
    for (int i = 0; i < n+q; i++) {
        building[i].x = 1e7 - building[i].x;
    }
    calc();

    printf("Case #%d:\n", ++cCase);
    for (int i = 0; i < q; i++) {
        printf("%.10lf\n", (PI - ans[i]) / PI * 180.0);
    }
}

int main() {
    scanf("%d", &nCase);
    while (nCase--) {
        input();
        solve();
    }
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值