計算幾何

向量

向量相關的一些基本量計算

向量的模:\(|\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;
}

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

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