luogu3194/BZOJ1007/HNOI2008 水平可见直线 单调栈/计算几何入门

题意乍一看不是很明确…所以首先解释一下题干。

如上图,蓝色的是各式各样的直线,而我们要求的就是最上面的这一部分包含哪些直线。
就相当于求一个分段函数函数的极值。并且,我们称这些部分的集合为凸壳。
我们一点一点来思考。首先介绍一下单调栈的概念:
如果有一个栈,支持不断弹出以维护单调性,那么就称这个栈叫做单调栈。对单调性的维护方法是因题而异的。由于每个元素至多进入一次,所以其复杂度是 O(n) 的。
我们首先按照斜率从小到大的顺序排序。然后再以此为基准进行下面的思考。至于为什么这样做,下面再来分析。
思考一下就可以想象到,斜率最小和最大的应该是保留的,因为这是最边上的两段凸壳。

首先来看两条线的情况,结果就是红色的一段。显然,两条线都在凸壳内,我们定义交点是 P 点。将这两条线段加入栈中,由于斜率排过序,我们将紫色的称为1,蓝色的称为2。
然后这时加入一条线,同样由于斜率排过序,所以结果应该是两条绿线之一:

上面的绿线与2的交点在P的左边,并且观察可以得到,这条绿线取代了刚才的2号线。所以我们弹出2号,加入3号,栈的情况是{1, 3}
下面的绿线与2的交点在 P <script type="math/tex" id="MathJax-Element-837">P</script>的右边,那么中间就有一段凸壳并没有被取代,所以我们不弹出,然后加入3号,栈的情况是{1, 2, 3}。
如果斜率的符号发生改变,结果应该是相似的:

保留与否观察交点位置即可。
好像看出了些规律:如果一条线进入之后与上一条线的交点在上一个交点的左边,那么就弹出上一条线;在右边则保留。至于不巧一样的情况,画一个图就可以明白应该是弹出上一条线。
这个弹出和比较的过程应该一直持续到交点产生在右边为止。
至于判断交点,直接联立方程比较横坐标即可。
最后的问题是斜率相同,截距不同的情况。同样画个图yy一下:

如果一组直线斜率相同,并且作为凸壳的一段,那么一定取截距最大的一段,因为会把下面的都遮住。
实现上我们可以从大到小排序,只保留第一条,然后把剩下的都跳过就好辣
附上丑陋的代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
struct node{
    int id, A, B;
}line[50003];
int n;
int stack[50003], cnt = 0;
int ans[50003];
bool cmp(node a, node b) {
    return a.A == b.A ? a.B > b.B : a.A < b.A;
}
inline double solve(int x, int y) { //解方程
    return (line[x].B - line[y].B) * 1.0 / (line[x].A - line[y].A) * 1.0;
}
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n;
    for(int i = 1; i <= n; ++i) {
        line[i].id = i;
        cin>>line[i].A>>line[i].B;
    }
    sort(line + 1, line + n + 1, cmp);
    for(int i = 1; i <= n; ++i) {
        if(i != 1 && line[i].A == line[i - 1].A) continue; // 斜率相等
        while(solve(i, stack[cnt]) >= solve(stack[cnt], stack[cnt - 1]) && cnt > 1) {
            --cnt;
        }

        stack[++cnt] = i;
        ans[cnt] = line[stack[cnt]].id;
    }
    sort(ans + 1, ans + cnt + 1);
    for(int i = 1; i <= cnt; ++i)
        cout<<ans[i]<<" ";
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值