向量
向量相關的一些基本量計算
向量的模:\(|\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;
}