我一直以来都挺不想学这个玩意儿的......
奈何最近经常碰到凸包啊半平面交之类的题,还有平面图
所以还是做一下吧
平面向量及其坐标表示
平面几何图形的基础定义、公理、定理
向量是个非常方便的东西,可以把很多平面几何空间几何里面用笛卡尔坐标暴力算很麻烦的东西变得很简单,所以一定要熟练运用
以下约定向量随着字母表的顺序a...z,依次对应坐标下标1...26
坐标表示:$\mathbf{a}=(x_1,y_1)$
向量的模:就是长度,$|\mathbf{a}|=\sqrt{x_1^2+y_1^2}$
$\mathbf{a}\ast\mathbf{b}=x_1x_2+y_1y_2$
可以用来算两个直线的夹角:求出两个直线$a,b$的向量$\mathbf{a},\mathbf{b}$
夹角$cos<a,b>=\frac{\mathbf{a}\ast\mathbf{b}}{|\mathbf{a}||\mathbf{a}|}$
实际上叉积这个概念是定义于空间向量里面的:
对于空间向量$\mathbf{a}={x_1,y_1,z_1}$,
空间向量叉积:$\mathbf{a}\times\mathbf{b}=(y_1z_2-y_2z_1,x_2z_1-x_1z_2,x_1y_2-x_2y_1)$
注意这个式子得到的是一个空间向量
那么对于平面向量来说,我们把$z$当做0,可以得到一个实数结果:
平面向量叉积:$\mathbf{a}\times\mathbf{b}=x_1y_2-x_2y_1$
这玩意儿有什么用呢?
它等于从$\mathbf{a}$旋转到$\mathbf{b}$的过程中构成的有向平行四边形**的面积(也就是可以是负数)
这里逆时针旋转为正,顺时针为负
那么它可以算什么呢?我们后面再说
直线有很多种存储方法:
可以存储直线上两个端点,常见于半平面交中
可以对于直线方程$Ax+By+C=0$存储$A,B,C$,这种比较不直观,相对来说用的少一点
可以对于直线方程$y=kx+b$存储$k,b$,分别是斜率和$y$轴截距,这种比较直观,常见于斜率相关的处理中
当然了,用哪一种最好还是依照各位自己的习惯,适合自己的才是最好的
最直观的的方式一般是求出$k$和$b$,用直线上两点$A,B$列方程
$k=\frac{y_2-y_1}{x_2-x_1}$
$b=y_1-x_1\ast k$
这个大家都学过,用直线方程$Ax+By+C=0$直接求
$dis=|\frac{Ax_0+By_0+C}{\sqrt{A^2+B^2}}|$
也可以用直线上两点坐标,构成三角形,求高即可
这里我们就会看到平面向量叉积的第一个使用:用从向量(这里也就是有向直线)上任意两点指向目标点的两个向量的叉积的正负,可以判断点和直线的位置关系
更准确地:设有向直线上按照顺序排列的两个点$A,B$,目标点$C$,则$\overrightarrow{AC}\times\overrightarrow{BC}$为正则$C$在$\overrightarrow{AB}$右侧,否则在左侧
可以画个图体会一下
如果有直线方程的话直接解方程即可
这里讲一个用两个直线上四个点求交点的方法:
设两条直线上四点分别为$A,B$和$C,D$,坐标为$(x_{1...4},y_{1...4})$
我们把点变成位置向量
令$v1=(A-D)\times(B-D),v2=(A-C)\times(B-C)$,那么交点的位置向量(位矢)为$D+(v1/(v1-v2))*(C-D)$
这个东西也是画个图就出来了:求出的两个平行四边形面积之比正好等于交点到$C,D$的距离之比,只不过其中一个是正的一个是负的,所以那里是$(v1-v2)$
代码如下:
struct p{
long double x,y;
p(long double xx=0.0,long double yy=0.0){x=xx;y=yy;}
};
inline p operator *(const p &a,const long double &b){return p(a.x*b,a.y*b);}
inline long double operator *(const p &a,const p &b){return a.x*b.y-a.y*b.x;}//'x-multiple' of planary vector
inline p operator -(const p &a,const p &b){return p(a.x-b.x,a.y-b.y);}
inline p operator +(const p &a,const p &b){return p(a.x+b.x,a.y+b.y);}
struct seg{
p a,b;long double k;
seg(p aa=p(),p bb=p(),long double kk=0.0){a=aa;b=bb;k=kk;}
};
inline p cross(const seg &x,const seg &y){//calculate the intersection using planary vector
long double v1=(x.a-y.b)*(x.b-y.b);
long double v2=(x.a-y.a)*(x.b-y.a);
long double c=v1/(v1-v2);
p re=(y.b+((y.a-y.b)*c));
return re;
}
基本上是很好理解的:一堆直线的右边那一半平面的交就是半平面交【听起来贼好理解是不是】
操作也比较简单,放一个dalao的链接在这里(懒得自己写了23333)
1.以逆时针为正方向,建边(输入方向不确定时,可用叉乘求面积看正负得知输入的顺逆方向)
2.对线段根据极角排序
3.去除极角相同的情况下,位置在右边的边
4.用双端队列储存线段集合,遍历所有线段
5.判断该线段加入后对半平面交的影响(对双端队列的头部和尾部进行判断,因为线段加入是有序的)
(这里判断的方式是看最前面两个或者最后面两个的交点是不是在新加入线的右边,可以画个图理解一下)
6.如果某条线段对于新的半平面交没有影响,则从队列中剔除掉(双端队列头尾删除)
7.最后剩下的线段集合,即使最后要求的半平面交
代码给一下:
这份代码用双端队列求出半平面交的直线集合,以及集合内直线的交点,再用交点位矢求出面积
inline long double solve(){
int i,head=1,tail=0,flag;long double re=0;
sort(a+1,a+m+1);
for(i=1;i<=m;i++){
flag=0;
while(head<=tail&&(!sign(a[i].k-q[tail].k))){//get rid of segments at same k
if((q[tail].a-a[i].a)*(q[tail].a-a[i].b)>=0) tail--;//if old one is to the right of current one, delete it
else{flag=1;break;}//or else, the current one shall be deleted
}
if(flag) continue;
while(head<tail&&right(rt[tail],a[i])) tail--;//check if the intersection is to the right, if so delete the foremost/backmost segment
while(head<tail&&right(rt[head+1],a[i])) head++;
q[++tail]=a[i];
if(head<tail) rt[tail]=cross(q[tail-1],q[tail]);
}
while(head<tail&&right(rt[tail],q[head])) tail--;
while(head<tail&&right(rt[head+1],q[tail])) head++;
rt[head]=rt[tail+1]=cross(q[head],q[tail]);//mind that the first and last points are adjacent
for(i=head;i<=tail;i++){
re+=rt[i]*rt[i+1];
}
return re*0.5;
}
咕咕咕(主要是我没见过纯凸包的......)周末补
所谓平面图,就是一个图,所有的边可以画在平面上,互相之间不在定点之外的地方相交
一般会给出平面图每个点的坐标、每个边是直线
可以看到,一个平面图会把一个平面划分成一个无限大的区域和若干面积有限的区域
我们可以使用最左转线算法,把每一个区域变成点、每一个原图边变成新图中相邻两个区域之间的边,这样就构建出了平面图的对偶图
先把所有边改成双向的(不过一般题目都会给双向的)
对于原图每一个点的出边,按照其出边的斜率进行排序。
以如下方式遍历图中所有的边:
找一条没有访问过的边(u,v),标记之
对于点v,找到v的出边中极点序在(v,u)后面的那一个,并重复上一行的过程,直到某一次找到的下一条边是标记过的
每做一次这个过程,我们就会找到一个 对偶图定点(亦即原图中的一个区域)
一次过程中访问到的所有边就是这个区域的边界(如果是无限大的那个区域,就是分割无限大和其他人的所有边)
因为我们所有的边都是双向的,而每一条双向边分割了两个区域,所以最终我们对于原图边和区域的一一对应,可以不重复不遗漏
因为“找到极点序中的上一个”这个操作就是找到这条边的下一条中“最向左转”的一条边,所以算法称作最左转线
贴个代码,来自这道题:
for(i=1;i<=n;i++){
tot=e[i].size();ee.clear();
for(j=0;j<tot;j++){
ee.push_back(mp(atan2(y[e[i][j].first]-y[i],x[e[i][j].first]-x[i]),e[i][j].first));
}
sort(ee.begin(),ee.end());
for(j=0;j<tot-1;j++){//最左转线预处理:标记每一个点的后继
suf[i][ee[j].second]=ee[j+1].second;
}
if(tot) suf[i][ee[tot-1].second]=ee[0].second;
}
for(i=1;i<=n;i++){
for(j=0;j<e[i].size();j++){
pos=e[i][j].first;from=i;
if(col[i][pos]) continue;
cnt++;
col[i][pos]=cnt;
suml[cnt]+=light[pos];
sumd[cnt]+=dark[pos];
while(1){//求出一个区域
next=suf[pos][from];
if(col[pos][next]) break;
from=pos;pos=next;
col[from][pos]=cnt;
suml[cnt]+=light[pos];
sumd[cnt]+=dark[pos];
}
}
}
点定位,顾名思义,就是定位平面上的点位于平面图分割出来的那个区域里面
可以用排序+平衡树实现$O(n\log n)$,也是周末补上
原文:https://www.cnblogs.com/dedicatus545/p/10639244.html