半平面交简介(BZOJ2618)

定义

半平面:平面上的直线及其一侧的部分,可以用 Ax+By+c0 A x + B y + c ≥ 0 表示。

在一个有界区域里半平面或半平面的交是一个凸多边形区域。

n个半平面的交是一个至多n条边的凸多边形。
其实半平面交就是线性规划方程组的解集。

举个例子,定义有向直线的右边为半平面,那么半平面交就是下图的红色区域。

这里写图片描述

求解

可以发现半平面交是一个凸多边形(当然也有是一个点/一条线/空集的情况)。当半平面交无界时,通常再加四个半平面控制边界(就像框了一个矩形一样)。

求解半平面交和凸包有些类似,其大致步骤如下:

1.把所有半平面按照和x轴的夹角排序,在x轴下方的为负。当夹角相同时保留限制最紧的那个。
2.类似凸包的单调栈,维护一个单调队列,假设我们现在新加了一个半平面 l l
3.当队尾的两条直线的交点在l的右边时,队尾要被弹掉。如这张图,红色的为 l l q为要弹掉的半平面。因为之前排过序,保证了弹掉的一定是队尾。队首也这么做一遍。
这里写图片描述
4.因为半平面交是首尾相接的,所以后面会有一些无用的半平面。这时就要把队首插入队尾维护一遍。
5.最后队列里剩下的就是解了。

模板

BZOJ2618为例:

有一个计算凸多边形的面积公式:

2S=ni=1ai×ai+1+a1×an 2 S = ∑ i = 1 n a i → × a i + 1 → + a 1 → × a n →

代码:

#include<cmath>
#include<cctype>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 1005
#define F inline
using namespace std;
typedef double DB;
struct P{ DB x,y; }a[N],tmp[N];
struct L{ P a,b; DB ag; }t[N],q[N];
int n,k,nd; DB ans;
F char readc(){
    static char buf[100000],*l=buf,*r=buf;
    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
    if (l==r) return EOF; return *l++;
}
F int _read(){
    int x=0,f=1; char ch=readc();
    while (!isdigit(ch)) { if (ch=='-') f=-1; ch=readc(); }
    while (isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=readc();
    return x*f;
}
F P operator - (P a,P b){ return (P){a.x-b.x,a.y-b.y}; }
F DB operator * (P a,P b){ return a.x*b.y-a.y*b.x; }
F bool operator < (L x,L y){
    return x.ag!=y.ag?x.ag<y.ag:(x.b-x.a)*(y.b-x.a)>0;
}
F P IS (L x,L y){//交点
    DB p=(y.b-x.a)*(x.b-x.a),q=(x.b-x.a)*(y.a-x.a),z=p/(p+q);
    return (P){y.b.x+(y.a.x-y.b.x)*z,y.b.y+(y.a.y-y.b.y)*z};
}
F bool pd(L x,L y,L z){ return (z.b-z.a)*(IS(x,y)-z.a)<0; }
F void Hpi(){//半平面交
    sort(t+1,t+k+1); int l=1,r=nd=0;
    for (int i=1;i<=k;i++)
        nd+=t[i].ag!=t[i-1].ag,t[nd]=t[i];
    k=nd,nd=0,q[++r]=t[1],q[++r]=t[2];
    for (int i=3;i<=k;i++){
        while (l<r&&pd(q[r-1],q[r],t[i])) r--;
        while (l<r&&pd(q[l+1],q[l],t[i])) l++;
        q[++r]=t[i];
    }
    while (l<r&&pd(q[r-1],q[r],q[l])) r--; q[r+1]=q[l];
    for (int i=l;i<=r;i++) a[++nd]=IS(q[i],q[i+1]);
}
F void calc(){//计算面积
    if (nd<3) return; a[++nd]=a[1];
    for (int i=1;i<=nd;i++) ans+=a[i]*a[i+1];
    ans=abs(ans)/2;
}
int main(){
    n=_read();
    for (int i=1;i<=n;i++){
        int m=_read();
        for (int j=1;j<=m;j++)
            tmp[j].x=_read(),tmp[j].y=_read();
        tmp[m+1]=tmp[1];
        for (int j=1;j<=m;j++)
            t[++k].a=tmp[j],t[k].b=tmp[j+1];
    }
    for (int i=1;i<=k;i++)
        t[i].ag=atan2(t[i].b.y-t[i].a.y,t[i].b.x-t[i].a.x);
    return Hpi(),calc(),printf("%.3f",ans),0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值