BZOJ 1007: [HNOI2008]水平可见直线

Description

 在xoy直角坐标平面上有n条直线L1,L2,…Ln,若在y值为正无穷大处往下看,能见到Li的某个子线段,则称Li为可见的,否则Li为被覆盖的.

例如,对于直线:

L1:y=x; L2:y=-x; L3:y=0

则L1和L2是可见的,L3是被覆盖的.

给出n条直线,表示成y=Ax+B的形式(|A|,|B|<=500000),且n条直线两两不重合.求出所有可见的直线.

Input

第一行为N(0 < N < 50000),接下来的N行输入Ai,Bi

Output

从小到大输出可见直线的编号,两两中间用空格隔开,最后一个数字后面也必须有个空格

Sample Input

1
2
3
4
3
-1 0
1 0
0 0

Sample Output

1
1 2

Solution

拿来笔和纸,手动模拟一下这个题,发现不管直线是什么,最后有贡献的直线一定会围成一个“V”形。然后这个V形从左向右,直线的斜率不断变大;还有一点很显然的是随着斜率的变大,两条直线交点横坐标也是不断变大的。

这对这道题有什么卵用呢?

由于这条性质,我们很容易就能用栈来维护V上的直线。先将直线按照斜率排序,每次新加入的直线和栈顶的比较即可。设栈为stack,共有tail条直线,那么如果新直线i与stack[tail]交点在stack[tail]与stack[tail-1]交点的左边(横坐标小),那么就弹出栈顶元素。直到交点在右边(横坐标大)或者栈中仅有一个元素,插入新直线即可。

(蒟蒻调这个题的时候,红字愣是没看见。。。WA了两次。。。QAQ)

Code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <cstdio>
#include <algorithm>
const int maxn = 50009;
int n, id[maxn], H[maxn], tot = 0, sta[maxn];
double A[maxn], B[maxn], k[maxn], b[maxn];
bool cmp(int x, int y) { return A[x] == A[y] ? B[x] < B[y] : A[x] < A[y]; }
double calc(int i, int j) { return (b[j]-b[i]) / (k[i]-k[j]); }
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i) {
        scanf("%lf%lf", A+i, B+i);
        id[i] = i;
    }
    std::sort(id+1, id+n+1, cmp);
    for (int j = 1; j < n; ++j) {
        int i = id[j], ii = id[j+1];
        if (A[i] != A[ii]) {
            k[++tot] = A[i];
            b[tot] = B[i];
            H[tot] = i;
        }
    }
    k[++tot] = A[id[n]];
    b[tot] = B[id[n]];
    H[tot] = id[n];
    int tail = 0;
    for (int i = 1; i <= tot; ++i) {
        while (tail > 1 && calc(i, sta[tail]) <= calc(sta[tail], sta[tail-1]))
            --tail;
        sta[++tail] = i;
    }
    for (int i = 1; i <= tail; ++i) id[i] = H[sta[i]];
    std::sort(id+1, id+tail+1);
    printf("%d", id[1]); for (int i = 2; i <= tail; ++i) printf(" %d", id[i]);
}