xiaodao在ACdream某场比赛上出的一道不错的计算几何题目,做法不唯一,此处我采用的线段树方法。【PS:听说更优解是多次凸包维护】
题目链接:http://acdream.info/problem?pid=1057
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.
n
A1 A2 ... An
m...
(here following the m query.)
5 2 3 3 3 4 3 3 2 4
3 2 5
题目大意比较简单,不多做赘述。
首先,假设我们站在无限高的层面上,我们自然可以俯瞰到所有建筑物,然后随着高度的下降,某个建筑有可能会被在其前面的某个建筑所遮挡。所谓“遮挡”,即人所在的点(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;
}