「HNOI2008」水平可见直线

传送门

半平面交(×)一次函数(√)

观察一下题面,很显然他是让你维护一个凸壳。

我们考虑把所有直线按照斜率从小到大排序,相同斜率的取在 (y) 轴上的截距最大的。

然后我们类似于求凸包地,依次将直线试着加入一个单调栈中,弹出一条直线的条件就是它被挡住了。

考虑栈顶被挡住的情况:由于我们是按照斜率从小到大加直线的,画一个图不难发现当前直线和栈顶的交点,在栈顶和栈顶下面一个的交点的左边(可以重合),满足条件时就把栈顶弹掉。

求交点就是解一个二元一次方程组。

最后留在栈中的就是可以被看见的直线。

方便起见,可以特判一下去重后 (n = 1) 的情况。

参考代码:

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

const int _ = 5e4 + 5;

int n, now, top, ans[_]; struct node { double k, b; int id; } t[_], stk[_];

int cmp(node x, node y) { return x.k < y.k || (x.k == y.k && x.b > y.b); }

double cross(node x, node y) { return (x.b - y.b) / (y.k - x.k); }

int main() {
#ifndef ONLINE_JUDGE
    freopen("cpp.in", "r", stdin), freopen("cpp.out", "w", stdout);
#endif
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%lf %lf", &t[i].k, &t[i].b), t[i].id = i;
    sort(t + 1, t + n + 1, cmp);
    t[++now] = t[1];
    for (int i = 2; i <= n; ++i)
        if (t[i].k != t[i - 1].k) t[++now] = t[i];
    if (now == 1) { printf("%d ", t[1].id); return 0; }
    stk[++top] = t[1], stk[++top] = t[2];
    for (int i = 3; i <= now; ++i) {
        while (top >= 2 && cross(t[i], stk[top]) <= cross(stk[top - 1], stk[top])) --top;
        stk[++top] = t[i];
    }
    for (int i = 1; i <= top; ++i) ans[stk[i].id] = 1;
    for (int i = 1; i <= n; ++i) if (ans[i]) printf("%d ", i);
    return 0;
}
原文地址:https://www.cnblogs.com/zsbzsb/p/13160417.html