[关闭]
@dxbdly 2021-08-29T05:38:38.000000Z 字数 9076 阅读 235

G2020 19寒假集训Day6

信息学——19寒假集训


今天是G2020寒假集训Day6,今天的主题是毒瘤的 计算几何

数学不是很好,有写得不对或写得不是很清楚的请见谅。

:有些东西的确写得不准确啊……凑合着看吧,咕了。

一. 向量

定义:

向量的定义:

  1. 向量表示的是一个有大小和方向的量,
  2. 在平面坐标系下它与点一样也用两个数来表示。
  3. 这两个数的实际含义是将这个向量的起点移至原点后终点的坐标。

我们用来表示一个向量。其实我们可以将向量看成点的另一种形式。

向量的基本运算

关于向量的四则运算,还是比较简单的

直接放代码啦~~~

  1. //The code is from dxbdly
  2. struct Vec{
  3. double x,y;
  4. Vec(double X=0,double Y=0):x(X),y(Y){}
  5. Vec operator+(const Vec &k)const {return Vec(x+k.x,y+k.y);}
  6. Vec operator-(const Vec &k)const {return Vec(x-k.x,y-k.y);}
  7. Vec operator*(const int &k)const {return Vec(x*k,y*k);}
  8. Vec operator/(const int &k)const {return Vec(x/k,y/k);}
  9. }

应该能看懂吧,下面看几个比较特殊的。

向量的点积

设 向量,向量

则两向量的点积:

点积的几何意义

若两向量的夹角为,他们的点积为:

使用点积判断两向量夹角情况

(PS:点积满足交换律)

代码

  1. //The code is from dxbdly
  2. inline double dj(Vec a,Vec b)//点积
  3. {
  4. return a.x*b.x+a.y*b.y;
  5. }

向量的叉积

叉积与点积比较相似。

设 向量,向量

两向量的叉积

叉积的几何意义

所以我们容易求出两向量构成的三角形面积

使用叉积判断两向量位置关系

  1. 补充:
  2. 判断线与向量的位置关系只需在直线上任取一向量
  3. 看两向量之间的位置关系即可。

(PS:注意叉积不满足交换律!!!)

代码:

  1. //The code is from dxbdly
  2. inline double cj(Vec a,Vec b)
  3. {
  4. return a.x*b.y-a.y*b.x;
  5. }

向量的极角

对于一个向量我们可以通过函数(内有)得到他的极角

向量的旋转

设 向量旋转了

旋转公式:

矩阵形式:

二.凸包

1.凸包定义与性质:

定义:

  1. 凸包:平面凸包是指覆盖平面上n个点的最小的凸多边形。
  2. 上凸壳:凸包的上半部分
  3. 下凸壳:凸包的下半部分。

性质:

  1. 凸壳的凸性:对于一个凸壳,所有线段的斜率满足单调性。

2.Graham算法

题面

算法本质:

  1. Graham扫描算法维护一个凸壳
  2. 通过不断在凸壳中加入新的点和去除影响凸性的点
  3. 最后形成凸包

算法流程:

  1. 1. 排序
  2. 2. 扫描

我们先将所有向量按极角序排序。

然后维护一个存点的栈要求栈内斜率单调(满足凸性)。

我们每次将一个点加入栈中,判断是否会与前面冲突。

如果有冲突将前面的点删除即可,最后留下的点便是凸包。

判断的方法么:

  1. //The code is from dxbdly
  2. inline bool check(Vec a1,Vec a2,Vec b1,Vec b2)
  3. {
  4. return (a2-a1)*(b2-b1)<0.0;
  5. }

总代码:(不是我写的……)

  1. //The code is not from dxbdly
  2. #include<iostream>
  3. #include<cstdio>
  4. #include<cmath>
  5. #include<algorithm>
  6. using namespace std;
  7. int n,top;
  8. double ans;
  9. struct data{
  10. double x,y;
  11. }p[10001],s[10001];
  12. double dis(data a,data b)
  13. {return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));}
  14. double mul(data p1,data p2,data p0)
  15. {return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);}
  16. inline bool cmp(data a,data b)
  17. {
  18. if(mul(a,b,p[0])==0)return dis(a,p[0])<dis(b,p[0]);
  19. return mul(a,b,p[0])>0;
  20. }
  21. void graham()
  22. {
  23. top=2;data t;
  24. int k=0;
  25. for(int i=1;i<n;i++)
  26. if((p[k].y>p[i].y)||(p[k].y==p[i].y&&p[k].x>p[i].x))k=i;
  27. t=p[0];p[0]=p[k];p[k]=t;
  28. sort(p+1,p+n,cmp);
  29. s[0]=p[0],s[1]=p[1],s[2]=p[2];
  30. for(int i=3;i<n;i++)
  31. {
  32. while(top&&mul(p[i],s[top],s[top-1])>=0)
  33. top--;
  34. s[++top]=p[i];
  35. }
  36. s[++top]=p[0];
  37. for(int i=0;i<top;i++)
  38. ans+=dis(s[i],s[i+1]);
  39. }
  40. int main()
  41. {
  42. //freopen("fc.in","r",stdin);
  43. //freopen("fc.out","w",stdout);
  44. scanf("%d",&n);
  45. for(int i=0;i<n;i++)
  46. scanf("%lf%lf",&p[i].x,&p[i].y);
  47. graham();
  48. printf("%.2lf",ans);
  49. return 0;
  50. }

3.Andrew算法

算法思路:

  1. 1.x坐标为第一优先级,按y坐标为第二优先级排序。
  2. 2.分别维护上下凸壳,将两个凸壳合并形成凸包。

直接放代码:

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. #define int long long
  4. #define INF 1e12
  5. using namespace std;
  6. inline int read()
  7. {
  8. int x=0;
  9. bool f=0;
  10. char c=getchar();
  11. while (!isdigit(c))
  12. f|=(c=='-'),c=getchar();
  13. while (isdigit(c))
  14. x=(x<<3)+(x<<1)+(c^48),c=getchar();
  15. return f?-x:x;
  16. }
  17. int n,top;
  18. struct Vec{
  19. double x,y;
  20. }point[100005];
  21. Vec st[100005];
  22. double ans;
  23. inline bool operator <(Vec a,Vec b)
  24. {
  25. return a.x!=b.x?a.x<b.x:a.y<b.y;
  26. }
  27. inline Vec operator -(Vec a,Vec b)
  28. {
  29. return (Vec){b.x-a.x,b.y-a.y};
  30. }
  31. inline double operator *(Vec a,Vec b)
  32. {
  33. return a.x*b.y-a.y*b.x;
  34. }
  35. inline bool check(Vec a1,Vec a2,Vec b1,Vec b2)
  36. {
  37. return (a1-a2)*(b1-b2)<0.0;
  38. }
  39. inline double get_dis(Vec a,Vec b)
  40. {
  41. return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
  42. }
  43. inline void get_up()
  44. {
  45. top=0,st[++top]=point[1],st[++top]=point[2];
  46. for (register int i=3;i<=n;++i)
  47. {
  48. while (top>1&&check(st[top-1],st[top],st[top],point[i]))
  49. top--;
  50. st[++top]=point[i];
  51. }
  52. for (register int i=1;i<top;++i)
  53. ans+=get_dis(st[i],st[i+1]);
  54. }
  55. inline void get_down()
  56. {
  57. top=0,st[++top]=point[n],st[++top]=point[n-1];
  58. for (register int i=n-2;i>=1;--i)
  59. {
  60. while (top>1&&check(st[top-1],st[top],st[top],point[i]))
  61. top--;
  62. st[++top]=point[i];
  63. }
  64. for (register int i=1;i<top;++i)
  65. ans+=get_dis(st[i],st[i+1]);
  66. }
  67. signed main(){
  68. n=read();
  69. for (register int i=1;i<=n;++i)
  70. scanf("%lf%lf",&point[i].x,&point[i].y);
  71. sort(point+1,point+n+1);
  72. get_up(),get_down();
  73. printf("%0.2lf",ans);
  74. return 0;
  75. }

三.半平面交

题面

1.定义:

  1. 半平面:由一条向量将平面分为两半,其中一半为半平面。
  2. 半平面交:所有半平面的最大相交部分组成的多边形。

2.求法:

将线转换为两个向量表示,考虑与凸包相似的求法,将线段按向量极角序排序,维护一个栈,使其满足凸性。

就可以了吗???

答案是肯定不行的,比如下面这种情况:

你会发现,加入新的线(蓝线)后与之前的首尾线段都有冲突!

所以我们应该用单调队列维护

将半平面交的组成线段确定之后,我们需要求出这些线段的交点。

求交点的话直接用叉积搞一下即可,不多赘述(其实我不会)

代码:

  1. //The code is from dxbdly
  2. inline Vec get_inter(Line a,Line b)
  3. {
  4. double k1=(b.b-a.a)*(a.b-a.a),k2=(a.b-a.a)*(b.a-a.a);
  5. double cnt=k1/(k1+k2);
  6. return (Vec){b.b.x+(b.a.x-b.b.x)*cnt,b.b.y+(b.a.y-b.b.y)*cnt};
  7. }

我们还要会一件事:求任意边形面积

  1. 对于求任意n边形面积问题
  2. 我们采用分割法,将其分割成n-1个三角形。
  3. 对于每个三角形,我们利用叉积的几何意义求出其面积。
  4. n-1个三角形面积相加即可。

算法流程:

  1. 1.按极角序排序。
  2. 2.Graham算法求出半平面交的边集合。
  3. 3.求出所有交点坐标。
  4. 3.用叉积求出面积。

其中还有一些细节,在这里列举一些

(其实计算几何就是细节多)

  1. 1.对于平行的情况我们只取最左侧的边。
  2. 2.注意边界的处理。

具体的话可以根据代码看。

  1. //The code is from dxbdly
  2. #include<bits/stdc++.h>
  3. using namespace std;
  4. inline int read()
  5. {
  6. int x=0;
  7. char c=getchar();
  8. bool f=0;
  9. while (!isdigit(c))
  10. f=f|(c=='-'),c=getchar();
  11. while (isdigit(c))
  12. x=(x<<3)+(x<<1)+(c^48),c=getchar();
  13. return f?-x:x;
  14. }
  15. int n,m,tot,len;
  16. struct Vec{
  17. double x,y;
  18. }point[1505],ans[1505];
  19. struct Line{
  20. Vec a,b;
  21. double at;
  22. }line[1505],q[1505];
  23. inline Vec operator -(Vec a,Vec b)
  24. {
  25. return (Vec){a.x-b.x,a.y-b.y};
  26. }
  27. inline double operator *(Vec a,Vec b)
  28. {
  29. return (a.x*b.y)-(a.y*b.x);
  30. }
  31. inline bool operator <(Line a,Line b)
  32. {
  33. return a.at!=b.at?a.at<b.at:(a.b-a.a)*(b.b-a.a)>0;
  34. }
  35. inline Vec get_inter(Line a,Line b)
  36. {
  37. double k1=(b.b-a.a)*(a.b-a.a),k2=(a.b-a.a)*(b.a-a.a);
  38. double cnt=k1/(k1+k2);
  39. return (Vec){b.b.x+(b.a.x-b.b.x)*cnt,b.b.y+(b.a.y-b.b.y)*cnt};
  40. }
  41. inline bool check(Line a,Line b,Line c)
  42. {
  43. Vec inter=get_inter(a,b);
  44. return (c.b-c.a)*(inter-c.a)<0;
  45. }
  46. inline void work()
  47. {
  48. int head=1,tail=0,cnt=0;
  49. for (register int i=1;i<=tot;++i)
  50. {
  51. if (line[i].at!=line[i-1].at)
  52. cnt++;
  53. line[cnt]=line[i];
  54. }
  55. tot=cnt;
  56. q[++tail]=line[1],q[++tail]=line[2];
  57. for (register int i=3;i<=tot;++i)
  58. {
  59. while (head<tail&&check(q[tail-1],q[tail],line[i]))
  60. tail--;
  61. while (head<tail&&check(q[head+1],q[head],line[i]))
  62. head++;
  63. q[++tail]=line[i];
  64. }
  65. while (head<tail&&check(q[tail-1],q[tail],q[head]))
  66. tail--;
  67. while (head<tail&&check(q[head+1],q[head],q[tail]))
  68. head++;
  69. q[tail+1]=q[head];
  70. for (register int i=head;i<=tail;++i)
  71. ans[++len]=get_inter(q[i],q[i+1]);
  72. }
  73. inline double get_ans()
  74. {
  75. if (len<3)
  76. return 0;
  77. double sum=0;
  78. ans[++len]=ans[1];
  79. for (register int i=1;i<=len;++i)
  80. sum+=ans[i]*ans[i+1];
  81. return fabs(sum)/2;
  82. }
  83. int main(){
  84. n=read();
  85. for (register int i=1;i<=n;++i)
  86. {
  87. m=read();
  88. for (register int j=1;j<=m;++j)
  89. scanf("%lf%lf",&point[j].x,&point[j].y);
  90. point[m+1]=point[1];
  91. for (register int j=1;j<=m;++j)
  92. ++tot,line[tot].a=point[j],line[tot].b=point[j+1];
  93. }
  94. for (register int i=1;i<=tot;++i)
  95. line[i].at=atan2(line[i].b.y-line[i].a.y,line[i].b.x-line[i].a.x);
  96. sort(line+1,line+tot+1);
  97. work();
  98. printf("%0.3f",get_ans());
  99. return 0;
  100. }

4.旋转卡壳

这是一个有16种读音的算法

旋(xuan2)转(zhuan4)卡(qia3)壳(qiao4)

题面

是求凸包(或多边形)最长点对距离的方法。

算法思路不难,写起来真的卡壳……

算法原理:

  1. 过凸包上两点用两条平行线将凸包“卡”住
  2. 每次旋转两条平行线,使得始终有一条线贴着凸包的一条边。
  3. 设这样的两个点为对踵点,则答案一定在这些对踵点中。

至于实现:

  1. 构造出凸包。
  2. 枚举一条凸包上的边(模拟旋转)
  3. 找到其对应的对踵点。

那么如何寻找对应的对踵点尤为重要。

暴力

对于每一条边枚举所有点,然后找到最优点

时间复杂度

旋转卡壳

观察发现。

我们可以发现凸包上的边与每个点的三角形面积成单峰函数

所以我们在逆时针枚举边的时候,对应的对踵点也会逆时针变化,所以我们每次从上一点开始即可。

时间复杂度

总结一下算法流程:

  1. 1.构造出凸包。
  2. 2.枚举凸包上的边
  3. 3.从上一次的答案开始枚举对踵点
  4. 4.判断是否更新。

代码:(我没调出来……,所以不是我的)

  1. //The code is not from dxbdly
  2. #include<bits/stdc++.h>
  3. #define eps 1e-8
  4. #define inf 1000000000
  5. #define pa pair<int,int>
  6. #define ll long long
  7. using namespace std;
  8. int read()
  9. {
  10. int x=0,f=1;char ch=getchar();
  11. while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
  12. while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
  13. return x*f;
  14. }
  15. int n,top;
  16. double ans;
  17. double sqr(double x)
  18. {
  19. return x*x;
  20. }
  21. struct P{
  22. double x,y;
  23. P(){}
  24. P(double _x,double _y):x(_x),y(_y){}
  25. friend P operator +(P a,P b){
  26. return P(a.x+b.x,a.y+b.y);
  27. }
  28. friend P operator -(P a,P b){
  29. return P(a.x-b.x,a.y-b.y);
  30. }
  31. friend double operator*(P a,P b){
  32. return a.x*b.y-a.y*b.x;
  33. }
  34. friend double operator/(P a,P b){
  35. return a.x*b.x+a.y*b.y;
  36. }
  37. friend bool operator==(P a,P b){
  38. return fabs(a.x-b.x)<eps&&fabs(a.y-b.y)<eps;
  39. }
  40. friend bool operator!=(P a,P b){
  41. return !(a==b);
  42. }
  43. friend bool operator<(P a,P b){
  44. if(fabs(a.y-b.y)<eps)return a.x<b.x;
  45. return a.y<b.y;
  46. }
  47. friend double dis2(P a){
  48. return sqr(a.x)+sqr(a.y);
  49. }
  50. friend void print(P a){
  51. printf("%.2lf %.2lf\n",a.x,a.y);
  52. }
  53. }p[50005],q[50005];
  54. bool cmp(P a,P b)
  55. {
  56. if(fabs((b-p[1])*(a-p[1]))<eps)return dis2(a-p[1])<dis2(b-p[1]);
  57. return (a-p[1])*(b-p[1])>0;
  58. }
  59. void graham()
  60. {
  61. for(int i=1;i<=n;i++)
  62. if(p[i]<p[1])swap(p[i],p[1]);
  63. sort(p+2,p+n+1,cmp);
  64. q[++top]=p[1];q[++top]=p[2];
  65. for(int i=3;i<=n;i++)
  66. {
  67. while((q[top]-q[top-1])*(p[i]-q[top-1])<eps&&top>1)top--;
  68. q[++top]=p[i];
  69. }
  70. }
  71. void RC()
  72. {
  73. q[top+1]=q[1];
  74. int now=2;
  75. for(int i=1;i<=top;i++)
  76. {
  77. while((q[i+1]-q[i])*(q[now]-q[i])<(q[i+1]-q[i])*(q[now+1]-q[i]))
  78. {
  79. now++;
  80. if(now==top+1)now=1;
  81. }
  82. ans=max(ans,dis2(q[now]-q[i]));
  83. }
  84. }
  85. int main()
  86. {
  87. n=read();
  88. for(int i=1;i<=n;i++)
  89. p[i].x=read(),p[i].y=read();
  90. graham();
  91. RC();
  92. printf("%d",(int)ans);
  93. return 0;
  94. }

这里面有些代码没调出来,所以不是我写的,到时候调出来了再更新吧,先凑合一下。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注