zoukankan      html  css  js  c++  java
  • 计算几何

    向量

    向量相关的一些基本量计算

    向量的模:(|vec{AB}| = sqrt{(x^2 + y^2)})
    向量的简单表示:(vec{AB} = (x2 - x1, y2 - y1) = (x, y))

    点积

    公式

    [(x_1, y_1) cdot (x_2, y_2) = x_1x_2 + y_1y_2 = |vec{v}| |vec{u}|cos heta ]

    特别的,如果2个相同向量相乘:

    [vec{v} cdot vec{v} = |vec{v}| |vec{v}| = |vec{v}|^2 ]

    扩展

    那么我们可以利用这个式子来计算2个向量之间的夹角。

    [cos heta = frac{x_1x_2 + y_1y_2}{|vec{v}| |vec{u}|} ]

    同时,由上式可以看出,点积的正负由夹角决定,即:

    • ( heta < 90^circ)时,点积为正
    • ( heta = 90^circ)时,点积为0,2个向量互相垂直
    • ( heta > 90^circ)时,点积为负
      (Delta):点积满足交换律。

    叉积

    公式

    [(x_1, y_1) imes (x_2, y_2) = x_1y_2 - y_1x_2 ]

    扩展

    (vec{v} imes vec{u})恰好等于这2个向量组成的三角形的有向面积的2倍。

    所以2个向量组成的三角形面积为:(frac{vec{v} imes vec{u}}{2})

    叉积的正负由向量的位置关系所决定。

    • (vec{y})(vec{x})左边时,(vec{x} imes vec{y})为正
    • (vec{y})(vec{x})右边时,(vec{x} imes vec{y})为负
    • 如果(vec{y})(vec{x})方向相同时,(vec{x} imes vec{y})为零

    即如下图所示:
    3.png-9.8kB
    (当然判断方向的时候要用角度小的那边,用顺时针还是逆时针方向来判断)
    由此可见,叉积没有交换律。同时叉积也由于这个性质,常在维护凸包的过程中被使用。

    凸包

    极角排序

    其实也不知道这个是不是,,,就是这样:

    bool cmp(node a, node b)
    {return (a.x != b.x) ? a.x < b.x : a.y < b.y;}
    

    叉积维护

    极角排序后,依次遍历每个点,假设上2个点是(p_0)(p_1),新加入的点是(p_2),那么要根据(vec{p_0p_1} imes vec{p_1p_2})的值来判断。

    动态凸包

    Splay

    在插入一个点的时候,先找到如果可以放入凸包,那么应该放在哪里。
    然后在找前驱后继,如果比前驱后继优秀,那么就弹出前驱后继。
    重复以上过程,最后合并的时候可能细节比较多(判断这个点是否应该加入凸包等等)。
    因为每个点只会被删除一次,每次弹到不能弹就停下了,所以插入的总复杂度是(O(nlogn))

    但是如果询问的(k)不单调的话,就需要二分相切的点,然后再在splay上二分的查询,所以复杂度就(O(nlog^2n))的了
    缺点:

    • 代码长,细节多
    • 可能会常数大?
    • 查询复杂度(log^2)

    优点:

    • 在线做法
    • 比较无脑

    CDQ维护凸包

    先按照(k)排序,然后在(cdq)的时候左边按(x)排序,右边按(k)排序。
    相当于先打乱,再最后排成(x)升序的序列。
    这样的话,用左边的来更新右边的,因为左边(x)升序,右边(k)升序,所以就相当于是二者都单调了。
    因此注意要先处理完左边对右边的贡献,再递归右边区间。
    然后再把整个区间按照(x)排序,这样才可以保证在处理贡献的时候始终单调。
    注意到把整个区间按照(x)排序的时候已经把右侧的也递归处理完了,所以右边现在也是(x)升序的了,于是直接归并合并就好了。
    此外因为在一个区间没有全部处理完之前,(k)升序的条件都是要用的,所以右区间再分小区间的时候,也要保证分出来的小区间中右区间是单调的.当然因为这里是(cdq),所以先递归左边,最后递归右边就可以保证在中间进行计算的时候两边都合法了。
    缺点:

    • 只能离线

    优点:

    • 代码短,相对来说好写
    • 不管询问的斜率是否单调,总复杂度都是(O(nlogn))
    #include<bits/stdc++.h>
    using namespace std;
    #define R register int
    #define inf 1e9
    #define eps 1e-9
    #define AC 100400
    int n;
    int q[AC];
    double f[AC];
    struct day{
        double k,x,y,a,b,r;int id;
    }t[AC],m[AC];
    
    inline bool cmp(day a,day b)
    {
        return a.k < b.k;
    } 
    
    inline double getk(int i,int j)
    {
        if(fabs(t[i].x - t[j].x) <= eps) return inf;
        return (t[j].y - t[i].y) / (t[j].x - t[i].x);
    }
    
    void pre()
    {
        scanf("%d%lf",&n,&f[0]);
        for(R i=1;i<=n;i++) 
        {
            scanf("%lf%lf%lf",&t[i].a,&t[i].b,&t[i].r);
            t[i].k=-t[i].a / t[i].b,t[i].id=i;
        }
        sort(t+1,t+n+1,cmp);//先按照k排序,然后在cdq的时候左边按x排序,右边按k排序
    }/*也就相当与先打乱,然后最后排成x升序的序列
    这样的话,因为左边x升序,右边k升序,就相当于是单调的了,
    然后注意先处理完左边对右边的贡献,然后再递归右边区间,
    然后再把整个区间按照x排序,这样才可以保证在处理贡献时始终单调,
    因为在一个区间没有全部处理完之前,k升序的条件都是要用的,
    因为右区间再分小区间的时候也要保证分出来的小区间中右区间k升序*/
    
    inline void merge(int l,int r)//按x递增排序
    {
        int mid=(l + r) >> 1,ll=l,rr=mid+1;
        int tot=0;
    /*	for(R i=l;i<=mid;i++) printf("%.2lf ",t[i].x);
        printf(" + ");
        for(R i=mid+1;i<=r;i++) printf("%.2lf ",t[i].x);
        printf("
    =");*/
        while(ll <= mid && rr <= r)
        {
            if(t[ll].x < t[rr].x + eps) m[++tot]=t[ll++];
            else m[++tot]=t[rr++];
        }
        while(ll <= mid) m[++tot]=t[ll++];//error!!!这里是while啊喂
        for(R i=1;i<=tot;i++) t[l + i - 1]=m[i];
    /*	for(R i=l;i<=r;i++) printf("%.2lf ",t[i].x);
        printf("
    
    ");*/
    }
    
    void cdq(int l,int r)
    {
        if(l == r)
        {
            f[l]=max(f[l],f[l-1]);//每更新一次f就要更新x和y
            t[l].y=f[l] / (t[l].a * t[l].r + t[l].b),t[l].x = t[l].y * t[l].r;
            return ;
        }
        int mid=(l + r) >> 1,ll=l-1,rr=mid,top=0;
        for(R i=l;i<=r;i++)
            if(t[i].id <= mid) m[++ll]=t[i];
            else m[++rr]=t[i];//要保证只能用前面的更新后面的(相当于是从k升序的序列里按顺序调了几个出来,
            //放到左边,剩下的放右边,因为是按顺序拿的,并且本来就有序,所以拿出来后还是有序的
            //相当于倒着归并
        for(R i=l;i<=r;i++) t[i]=m[i];
        cdq(l,mid);//要先处理完左边的,这样处理完后左边就x升序了
        for(R i=l;i<=mid;i++)
        {
            while(top >= 2 && getk(q[top],i) + eps > getk(q[top-1],q[top])) --top;
            q[++top]=i;
        }
        int be=mid+1;
        for(R i=be;i<=r;i++)
        {
            while(top >= 2 && getk(q[top-1],q[top]) <= t[i].k + eps) --top;//...
            int now=q[top];
            f[t[i].id]=max(f[t[i].id],t[now].x * t[i].a + t[now].y * t[i].b);
        }
        cdq(mid+1,r),merge(l,r);
    }
    
    int main()
    {
    //	freopen("in.in","r",stdin);
        pre();
        cdq(1,n);
        printf("%.3lf
    ",f[n]);
    //	fclose(stdin);
        return 0;
    }
    

    半平面交(待填坑...)

  • 相关阅读:
    js,js中使用正则表达式
    web开发中文件下载
    EL表达式
    Servlet Filter
    压缩文件 乱码问题(转载)
    MFC CopyDirectory
    SaveFileDialog
    Create Window
    CDateTimeCtrl 设置时间
    键值表
  • 原文地址:https://www.cnblogs.com/ww3113306/p/10649795.html
Copyright © 2011-2022 走看看