计算几何

向量

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

向量的模:\(|\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 \theta\]

特别的,如果2个相同向量相乘:
\[\vec{v} \cdot \vec{v} = |\vec{v}| |\vec{v}| = |\vec{v}|^2\]

扩展

那么我们可以利用这个式子来计算2个向量之间的夹角。
\[cos \theta = \frac{x_1x_2 + y_1y_2}{|\vec{v}| |\vec{u}|}\]

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

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

    叉积

    公式

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

    扩展

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

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

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

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

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

凸包

极角排序

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

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} \times \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("\n=");*/
    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("\n\n");*/
}

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\n",f[n]);
//  fclose(stdin);
    return 0;
}

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章