ACdream 1057 Vision Field 线段树+二分查找

xiaodao在ACdream某场比赛上出的一道不错的计算几何题目,做法不唯一,此处我采用的线段树方法。【PS:听说更优解是多次凸包维护】

题目链接:http://acdream.info/problem?pid=1057

Vision Field

Time Limit: 2000/1000MS (Java/Others)  Memory Limit: 256000/128000KB (Java/Others)
Submit  Statistic  Next Problem

Problem Description

There are N buildings stand along the horizon line.

Each building are been represented as a vertical segment with two end points at (i, 0) and (i, Ai).

There are M queries in total.

For each query, we wonder know how many buildings you can see if you stand at (0, h).

N, M ≤ 10^6, both Ai && h is positive interger and ≤ 10^9.

Input

n

A1 A2 ... An

m...

(here following the m query.)

Output

For each query, print the result on a single line.

Sample Input

5
2 3 3 3 4
3
3
2
4

Sample Output

3
2
5

Hint

x 轴正半轴上分布着  n 个楼房  (i, Ai),问站在  (0, h) 往右可以看到多少楼房。

Source

xiaodao

Manager

nanae
Submit  Statistic














































题目大意比较简单,不多做赘述。

首先,假设我们站在无限高的层面上,我们自然可以俯瞰到所有建筑物,然后随着高度的下降,某个建筑有可能会被在其前面的某个建筑所遮挡。所谓“遮挡”,即人所在的点(0, y0)与遮挡楼(x1, y1)所构成的直线在被遮挡楼(x2, y2)处的y值大于y2,致使人看不见这个建筑。【以下称这种y为被遮挡楼通过遮挡楼得到的“投影”】

对于m次查询,倘若我们能够知道每一座建筑恰好被"遮挡"的时候,所对应的人的高度,那么我们将这些高度排序后,进行二分查找即可在mlogn的时间内在线查询。

那么如何得到这个高度呢?事实上,一座建筑能否被遮挡,只与其左侧相邻最近的未被遮挡的建筑有关。呃,xiaodao似乎给过证明。

那么基于以上条件,我们可以采用线段树,对于每一个节点,维护的是当前区间内最高“投影”所对应的楼号。而对应每一个楼号,我们还需要存储其左侧和右侧相邻的最近的未被遮挡的楼号。而后,我们每次取出整个区间的最高"投影"楼,存储其投影高度,将其删除并把左右两侧的楼连接起来,重复取n-1次即可(因为第一个楼无论多高都能看见)。


AC代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;
typedef long long LL;
#define LL(x) (x << 1)
#define RR(x) (x << 1|1)
#define MID(x, y) (x + y >> 1)
#define lson l, mid, LL(x)
#define rson mid + 1, r, RR(x)
const int MAXN = 100010;
const double INF = 1e15;
int Tree[MAXN<<2], A[MAXN], l[MAXN], r[MAXN], top;
vector ans;
int n, m;
double height(int x) {
    if(l[x] == -1) return -INF;
    return A[l[x]] + (double)(A[l[x]] - A[x]) * l[x] / (x - l[x]);
}
void Push_up(int x) {
    double LL_v = height(Tree[LL(x)]);
    double RR_v = height(Tree[RR(x)]);
    if(LL_v == RR_v) Tree[x] = min(Tree[LL(x)], Tree[RR(x)]);
    else if(LL_v > RR_v) Tree[x] = Tree[LL(x)];
    else Tree[x] = Tree[RR(x)];
}
void Build_Tree(int l, int r, int x) {
    if(l == r) {
        Tree[x] = l;
    } else {
        int mid = MID(l, r);
        Build_Tree(lson), Build_Tree(rson);
        Push_up(x);
    }
}
void link(int x) {
    r[l[x]] = r[x], l[r[x]] = l[x];
}
void Push_down(int l, int r, int x) {
    if(l == r) return ;
    int mid = MID(l, r);
    if(top <= mid) Push_down(lson);
    else Push_down(rson);
    Push_up(x);
}
void Solve() {
    top = Tree[1];
    ans.push_back(height(top));
    link(top);
    l[top] = -1, Push_down(2, n, 1);
    if(r[top] != n + 1)
        top = r[top], Push_down(2, n, 1);
}
int main() {
    while(~scanf("%d", &n)) {
        ans.clear();
        for(int i = 1; i <= n; i++) {
            scanf("%d", &A[i]);
            l[i] = i - 1, r[i] = i + 1;
        }
        Build_Tree(2, n, 1);
        ans.push_back(-INF);//第一座建筑
        for(int i = 0; i < n - 1; i++) Solve();
        sort(ans.begin(), ans.end());
        scanf("%d", &m);
        while(m--) {
            int tmp, pos;
            scanf("%d", &tmp);
            pos = lower_bound(ans.begin(), ans.end(), tmp) - ans.begin();
            printf("%d\n", pos);
        }
    }
    return 0;
}



你可能感兴趣的:(线段树,计算几何)