平面几何

1 篇文章 0 订阅
1 篇文章 0 订阅

凸包

题:

bzoj3203
bzoj1185
bzoj1069
bzoj2300
bzoj2961

解一:分治法

时间复杂度:O(n㏒n)。
思路:应用分治法思想,把一个大问题分成几个结构相同的子问题,把子问题再分成几个更小的子问题……。然后我们就能用递归的方法,分别求这些子问题的解。最后把每个子问题的解“组装”成原来大问题的解。
步骤:
1.把所有的点都放在二维坐标系里面。那么横坐标最小和最大的两个点 P1 和 Pn 一定是凸包上的点(为什么呢?用反证法很容易证明,这里不详讲)。直线 P1Pn 把点集分成了两部分,即 X 轴上面和下面两部分,分别叫做上包和下包。
2.对上包:求距离直线 P1Pn 最远的点,即下图中的点 Pmax 。
3.作直线 P1Pmax 、PnPmax,把直线 P1Pmax 左侧的点当成是上包,把直线 PnPmax 右侧的点也当成是上包。
4.重复步骤 2、3。
5.对下包也作类似操作。
这里写图片描述

然而怎么求距离某直线最远的点呢?我们还是用到解一中的公式:
这里写图片描述
设有一个点 P3 和直线 P1P2 。(坐标:p1(x1,y1),p2(x2,y2),p3(x3,y3))
对上式的结果取绝对值,绝对值越大,则距离直线越远。

注意:在步骤一,如果横坐标最小的点不止一个,那么这几个点都是凸包上的点,此时上包和下包的划分就有点不同了,需要注意。

解二:Jarvis步进法

时间复杂度:O(nH)。(其中 n 是点的总个数,H 是凸包上的点的个数)
思路:

纵坐标最小的那个点一定是凸包上的点,例如图上的 P0。
从 P0 开始,按逆时针的方向,逐个找凸包上的点,每前进一步找到一个点,所以叫作步进法。
怎么找下一个点呢?利用夹角。假设现在已经找到 {P0,P1,P2} 了,要找下一个点:剩下的点分别和 P2 组成向量,设这个向量与向量P1P2的夹角为 β 。当 β 最小时就是所要求的下一个点了,此处为 P3 。
这里写图片描述

注意:

找第二个点 P1 时,因为已经找到的只有 P0 一个点,所以向量只能和水平线作夹角 α,当 α 最小时求得第二个点。
共线情况:如果直线 P2P3 上还有一个点 P4,即三个点共线,此时由向量P2P3 和向量P2P4 产生的两个 β 是相同的。我们应该把 P3、P4 都当做凸包上的点,并且把距离 P2 最远的那个点(即图中的P4)作为最后搜索到的点,继续找它的下一个连接点。

解三:Graham扫描法

时间复杂度:O(n㏒n)
思路:Graham扫描的思想和Jarris步进法类似,也是先找到凸包上的一个点,然后从那个点开始按逆时针方向逐个找凸包上的点,但它不是利用夹角。
这里写图片描述
步骤:

1.把所有点放在二维坐标系中,则纵坐标最小的点一定是凸包上的点,如图中的P0。
2.把所有点的坐标平移一下,使 P0 作为原点,如上图。
3.计算各个点相对于 P0 的幅角 α ,按从小到大的顺序对各个点排序。当 α 相同时,距离 P0 比较近的排在前面。例如上图得到的结果为 P1,P2,P3,P4,P5,P6,P7,P8。我们由几何知识可以知道,结果中第一个点 P1 和最后一个点 P8 一定是凸包上的点。
(以上是准备步骤,以下开始求凸包)
以上,我们已经知道了凸包上的第一个点 P0 和第二个点 P1,我们把它们放在栈里面。现在从步骤3求得的那个结果里,把 P1 后面的那个点拿出来做当前点,即 P2 。接下来开始找第三个点:
4.连接P0和栈顶的那个点,得到直线 L 。看当前点是在直线 L 的右边还是左边。如果在直线的右边就执行步骤5;如果在直线上,或者在直线的左边就执行步骤6。
5.如果在右边,则栈顶的那个元素不是凸包上的点,把栈顶元素出栈。执行步骤4。
6.当前点是凸包上的点,把它压入栈,执行步骤7。
7.检查当前的点 P2 是不是步骤3那个结果的最后一个元素。是最后一个元素的话就结束。如果不是的话就把 P2 后面那个点做当前点,返回步骤4。
最后,栈中的元素就是凸包上的点了。
以下为用Graham扫描法动态求解的过程:
这里写图片描述

解四:Melkman算法

这里写图片描述
说真的,这个算法我也还没有看清。网上的资料也少的可怜,我暂且把网上的解释截个图在这里,往后搞懂以后再回来补上。
或者有人看懂了的,希望不吝指教,不甚感激!

扩展:

以上讨论的只是二维的凸包,如果延生为三维、多维的凸包问题呢?如何求解?
不过首先,二维凸包可以用来解决围栏问题、城市规划问题、聚类分析等等。但是三维、多维的凸包可能的使用范畴有哪些?
(凸包的原文链接:http://blog.csdn.net/bone_ace/article/details/46239187)

模板

//poj2187 Graham AC_code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<set>
#include<ctime>
#include<vector>
#include<queue>
#include<algorithm>
#include<map>
#include<cmath>
#define eps 1e-8
#define inf 1000000000
#define pa pair<int,int>
#define ll long long 
using namespace std;
int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int n,top;
double ans;
double sqr(double x)
{

    return x*x;
}
struct P{
    double x,y;
    P(){}
    P(double _x,double _y):x(_x),y(_y){}
    friend P operator +(P a,P b){
        return P(a.x+b.x,a.y+b.y);
    }
    friend P operator -(P a,P b){
        return P(a.x-b.x,a.y-b.y);
    }
    friend double operator*(P a,P b){
        return a.x*b.y-a.y*b.x;
    }
    friend double operator/(P a,P b){
        return a.x*b.x+a.y*b.y;
    }
    friend bool operator==(P a,P b){
        return fabs(a.x-b.x)<eps&&fabs(a.y-b.y)<eps;
    }
    friend bool operator!=(P a,P b){
        return !(a==b);
    }
    friend bool operator<(P a,P b){
        if(fabs(a.y-b.y)<eps)return a.x<b.x;
        return a.y<b.y;
    }
    friend double dis2(P a){
        return sqr(a.x)+sqr(a.y);
    }
    friend void print(P a){
        printf("%.2lf %.2lf\n",a.x,a.y);
    }
}p[50005],q[50005];
bool cmp(P a,P b)
{
    if(fabs((b-p[1])*(a-p[1]))<eps)return dis2(a-p[1])<dis2(b-p[1]);
    return (a-p[1])*(b-p[1])>0;
}
void graham()
{
    for(int i=1;i<=n;i++)
        if(p[i]<p[1])swap(p[i],p[1]);
    sort(p+2,p+n+1,cmp);
    q[++top]=p[1];q[++top]=p[2];
    for(int i=3;i<=n;i++)
    {
        while((q[top]-q[top-1])*(p[i]-q[top-1])<eps&&top>1)top--;
        q[++top]=p[i];
    }
}
void RC()
{
    q[top+1]=q[1];
    int now=2;
    for(int i=1;i<=top;i++)
    {
        while((q[i+1]-q[i])*(q[now]-q[i])<(q[i+1]-q[i])*(q[now+1]-q[i]))
        {
            now++;
            if(now==top+1)now=1;
        }
        ans=max(ans,dis2(q[now]-q[i]));
    }
}
int main()
{
    n=read();
    for(int i=1;i<=n;i++)
        p[i].x=read(),p[i].y=read();
    graham();
    RC();
    printf("%d",(int)ans);
    return 0;
}

旋转卡壳

题:

bzoj1185
bzoj1069

bzoj1069

题意:

在某块平面土地上有N个点,你可以选择其中的任意四个点,将这片土地围起来,当然,你希望这四个点围成的多边形面积最大。

分析:

先求凸包。
不规则四边形无法计算面积。便将其切成两个三角形。
朴素做法:枚举对角线 O(n2) 再枚举对角线两边的点 O(n) 总时间复杂度是$O(n^3)显然爆炸!

有一个奇怪的定理(不会证):两边的要求的点(构成最大面积的)会跟对角线旋转的方向一起在凸包上同向旋转。

高级做法:枚举对角线一端,在枚举另外一端时,我们所要求的两个点就无需全部枚举(详见代码~)这样时间复杂度就为 O(n2) 了。

q[top+1]=a[1];
    double res=0;
    for (int i=1;i<=top;i++){
        int a=i%top+1,b=(i+2)%top+1;
        for (int j=i+2;j<=top;j++){
            while (a%top+1!=j && cro(q[j],q[a],q[i])<cro(q[j],q[a+1],q[i]))
                a=a%top+1;
            while (b%top+1!=i && cro(q[b],q[j],q[i])<cro(q[b+1],q[j],q[i]))
                b=b%top+1;
            res=max(res,fabs(cro(q[i],q[j],q[a]))+fabs(cro(q[i],q[j],q[b])));
        }
    }
    return res;

bzoj1185

题意:

求一个最小的矩形包含所有的点。一共50000个点。

分析:

超神题解
先求凸包。
有一个奇怪的定理(不会证):凸包上的一条边一定与矩形的一条边重合。
枚举凸包上的每一条边,现在我们只有求出矩形的最右边的点、最左边的点和最上边点就好了。注意到这三个点也是随枚举的边旋转而同向旋转的哦~这然后在纸上瞎推推就可以鸟~~

模板

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>

using namespace std;
const int N=1001000;
const double eps=0.000000001;
int n,top;
double ansmj;
struct node{
    double x,y;
    node(){}
    node(double _x,double _y):x(_x),y(_y){}
    friend bool operator < (node a,node b){
        if (fabs(a.y-b.y)<eps)
            return a.x<b.x;
        return a.y<b.y;
    }
    friend node operator + (node a,node b){
        return node(a.x+b.x,a.y+b.y);
    }
    friend node operator - (node a,node b){
        return node(a.x-b.x,a.y-b.y);
    }
    friend double dis (node a){
        return sqrt(a.x*a.x+a.y*a.y);
    } 
    friend double operator * (node a,node b){
        return a.x*b.y-a.y*b.x;
    }
    friend double operator / (node a,node b){
        return a.x*b.x+a.y*b.y;
    }
    friend node operator * (node a,double x){
        return node(a.x*x,a.y*x);
    }
}q[N],a[N],ans[4];

bool cmp(node aa,node b){
    double cro=(aa-a[1])*(b-a[1]);
    if (fabs(cro)<eps)
        return dis(a[1]-aa)<dis(a[1]-b);
    return cro>0;
}

void graham(){
    int id=1;
    for (int i=2;i<=n;i++)
        if (a[i]<a[id])
            id=i;
    swap(a[1],a[id]);
    sort(a+2,a+n+1,cmp);
    for (int i=1;i<=n;i++){
        while (top>1 && (q[top]-q[top-1])*(a[i]-q[top])<eps) top--;
        q[++top]=a[i];
    }
    q[0]=q[top];
}

void gao(){
    int l=1,r=1,h=1;
    double L,R,H,D;
    for (int i=0;i<top;i++){
        D=dis(q[i]-q[i+1]);
        while ((q[i+1]-q[i])*(q[h+1]-q[i])>(q[i+1]-q[i])*(q[h]-q[i])-eps) h=(h+1)%top;
        while ((q[i+1]-q[i])/(q[r+1]-q[i])>(q[i+1]-q[i])/(q[r]-q[i])-eps) r=(r+1)%top;
        if (i==0) l=r;
        while ((q[i+1]-q[i])/(q[l+1]-q[i])<(q[i+1]-q[i])/(q[l]-q[i])+eps) l=(l+1)%top;
        L=(q[i+1]-q[i])/(q[l]-q[i])/D;
        R=(q[i+1]-q[i])/(q[r]-q[i])/D;
        H=(q[i+1]-q[i])*(q[h]-q[i])/D;

        H=fabs(H);

        double mianji=(R-L)*H;
        if (ansmj>mianji){
            ansmj=mianji;
            ans[0]=q[i]+(q[i+1]-q[i])*(R/D);
            ans[1]=ans[0]+(q[r]-ans[0])*(H/dis(q[r]-ans[0]));
            ans[2]=ans[1]-(ans[0]-q[i])*((R-L)/dis(q[i]-ans[0]));
            ans[3]=ans[2]-(ans[1]-ans[0]);
        }
    }
}

int main(){
    freopen("a.in","r",stdin);
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
        scanf("%lf%lf",&a[i].x,&a[i].y);
    graham();ansmj=213333333333333;
    gao();
    printf("%.5lf\n",ansmj);
    int mi=0;
    for (int i=0;i<=3;i++)
        if (ans[i]<ans[mi])
            mi=i;
    for (int i=0;i<=3;i++){
        printf("%.5lf %.5lf\n",ans[mi].x,ans[mi].y);
        mi=(mi+1)%4;
    }
}

半平面交

题:

bzoj2618YES
bzoj3190
bzoj2732YES
bzoj1038YES
bzoj1007YES
bzoj1137YES
bzoj3800

bzoj2618

题意:

给出多个凸多边形,顶点按逆时针给出。求它们的重叠面积。

分析:

直接用半平面交乱搞就好了。当模板题。

模板

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>

using namespace std;
const int N=1001000;
int n,cnt,tot;
double ans;
struct node{
    double x,y;
    node(){}
    node(double _x,double _y):x(_x),y(_y){}
    friend node operator - (node a,node b){
        return node(a.x-b.x,a.y-b.y);
    }
    friend node operator + (node a,node b){
        return node(a.x+b.y,a.y+b.y);
    }
    friend double operator * (node a,node b){
        return a.x*b.y-a.y*b.x;
    }
    friend double slope (node a,node b){
        return atan2(a.y-b.y,a.x-b.x);
    }
}p[N],a[N]; 
struct line{
    node st,ed;
    double sl;
    friend bool operator < (line a,line b){
        if (a.sl==b.sl)
            return (a.ed-a.st)*(b.ed-a.st)>0;
        return a.sl<b.sl;
    }
}b[N],q[N];

node jiaodian(line a,line b){
    double k1,k2,t;
    k1=(b.ed-a.st)*(a.ed-a.st);
    k2=(a.ed-a.st)*(b.st-a.st);
    t=k1/(k1+k2);
    node ans;
    ans.x=b.ed.x+(b.st.x-b.ed.x)*t;
    ans.y=b.ed.y+(b.st.y-b.ed.y)*t;
    return ans;
}

int onleft(line a,line b,line k){
    node dian=jiaodian(a,b);
    return (k.ed-k.st)*(dian-k.st)<0;
}

void jiao(){
    sort(b+1,b+cnt+1);
    for (int i=1;i<=cnt;i++){
        if (b[i].sl!=b[i-1].sl)
            tot++;
        b[tot]=b[i];
    }cnt=tot;tot=0;
    int l=1,r=0;
    for (int i=1;i<=cnt;i++){
        while (l<r && onleft(q[r-1],q[r],b[i])) r--;
        while (l<r && onleft(q[l+1],q[l],b[i])) l++;
        q[++r]=b[i];
    }
    while (l<r && onleft(q[r-1],q[r],b[l])) r--;
    while (l<r && onleft(q[l+1],q[l],b[r])) l++;
    q[r+1]=q[l];
    for (int i=l;i<=r;i++)
        p[++tot]=jiaodian(q[i],q[i+1]);
}

void getans(){
    if (tot<3) return;
    p[++tot]=p[1];
    for (int i=1;i<=tot;i++)
        ans+=p[i]*p[i+1];
    ans=fabs(ans)/2.0;
}

int main(){
    freopen("a.in","r",stdin);
    freopen("my.out","w",stdout);
    int T;scanf("%d",&T);
    while (T--){
        scanf("%d",&n);
        for (int i=1;i<=n;i++)
            scanf("%lf%lf",&a[i].x,&a[i].y);
        a[n+1]=a[1];
        for (int i=1;i<=n;i++){
            b[++cnt].st=a[i];
            b[cnt].ed=a[i+1];
            b[cnt].sl=slope(a[i+1],a[i]);
        }
    }
    jiao();
    getans();
    printf("%.3lf\n",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值