概念
用不严谨的话来讲,给定二维平面上的点集,凸包就是将最外层的点连接起来构成的凸多边型,它能包含点集中所有的点。
例子:假设平面上有p0~p12共13个点,过某些点作一个多边形,使这个多边形能把所有点都“包”起来。当这个多边形是凸多边形的时候,我们就叫它“凸包”。如下图:
求解(Graham扫描算法)
时间复杂度:O(nlogn)
思路:Graham扫描的思想是先找到凸包上的一个点,然后从那个点开始按逆时针方向逐个找凸包上的点,实际上就是进行极角排序,然后对其查询使用。
步骤:
1把所有点放在二维坐标系中,则纵坐标最小的点一定是凸包上的点,如图中的P0。
2把所有点的坐标平移一下,使 P0 作为原点,如上图。
3计算各个点相对于 P0 的极角 α ,按从小到大的顺序对各个点排序。当 α 相同时,距离 P0 比较近的排在前面。例如上图得到的结果为 P1,P2,P3,P4,P5,P6,P7,P8。我们由几何知识可以知道,结果中第一个点 P1 和最后一个点 P8 一定是凸包上的点。
(以上是准备步骤,以下开始求凸包)
以上,我们已经知道了凸包上的第一个点 P0 和第二个点 P1,我们把它们放在栈里面。现在从步骤3求得的那个结果里,把 P1 后面的那个点拿出来做当前点,即 P2 。接下来开始找第三个点:
4连接P0和栈顶的那个点,得到直线 L 。看当前点是在直线 L 的右边还是左边。如果在直线的右边就执行步骤**5;**如果在直线上,或者在直线的左边就执行步骤6。
如果在右边,则栈顶的那个元素不是凸包上的点,把栈顶元素出栈。执行步骤4。
6当前点是凸包上的点,把它压入栈,执行步骤7。
7检查当前的点 P2 是不是步骤3那个结果的最后一个元素。是最后一个元素的话就结束。如果不是的话就把 P2 后面那个点做当前点,返回步骤4。
8最后,栈中的元素就是凸包上的点了。
例子:
POJ1113【基础】
题目大意:
给出 n 个(1<=n<=1000)点和一个半径 r,求这若干个点的凸包周长加上以 r为半径的圆周长之和。
输入:
第一行有一个整数 n 和一个小数 r,中间有空格分开。接下来每一行有两个整数,表示一个点的 x y 坐标。坐标范围在(-10000,10000)以内。
输出:
一行,一个整数,即周长的四舍五入值。
#include<stdio.h>
#include<math.h>
#define eps 1e-7
#define MaxN 1001
#define pi 3.1415926535
struct Point
{
double x,y;
Point(double a=0,double b=0){x=a;y=b;}
};
inline double dis(Point &a,Point &b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
/*------Graham求凸包----*/
/*
得到sp-op和ep-op的叉积
>0时ep在opsp的逆时针方向,<0时顺时针,=0时共线
*/
inline double Cross(Point sp,Point ep,Point op)
{
return (sp.x-op.x)*(ep.y-op.y)-(ep.x-op.x)*(sp.y-op.y);
}
inline int cmp(Point &a,Point &b)
{
if (a.y==b.y) return (a.x<b.x);
return (a.y<b.y);
}
//采用eps的精度判断大/小于零
inline int epssgn(double x)
{
if (fabs(x)<eps) return 0;
else return x<0?-1:1;
}
//对所有的点进行一次排序
void QSort(Point p[],int l,int r)
{
int i=l,j=r;
Point mid=p[(l+r)/2],swap;
while (i<=j)
{
while (cmp(p[i],mid)) i++;
while (cmp(mid,p[j])) j--;
if (i<=j)
{
swap=p[i];
p[i]=p[j];
p[j]=swap;
i++;j--;
}
}
if (i<r) QSort(p,i,r);
if (l<j) QSort(p,l,j);
}
//n为原图的点数,p[]为原图的点(0~n-1),top为凸包点的数量(0~top-1),res[]为凸包点的下标,。
int Graham(Point p[],int n,int res[])
{
int i,len,top;
top=1;
QSort(p,0,n-1);
for (i=0;i<3;i++) res[i]=i;
for (i=2;i<n;i++)
{
while (top && epssgn(Cross(p[i],p[res[top]],p[res[top-1]]))>=0) top--;
res[++top]=i;
}
len=top;
res[++top]=n-2;
for (i=n-3;i>=0;i--)
{
while (top!=len && epssgn(Cross(p[i], p[res[top]], p[res[top-1]]))>=0) top--;
res[++top]=i;
}
return top;
}
/*------Graham求凸包----*/
Point p[MaxN];
int res[MaxN];
int main()
{
int n,i,chnum;
double r,ans;
freopen("poj1113.txt","r",stdin);
freopen("poj1113ans.txt","w",stdout);
while (scanf("%d%lf",&n,&r)!=EOF)
{
for (i=0;i<n;i++) scanf("%lf%lf",&p[i].x,&p[i].y);
chnum=Graham(p,n,res);
ans=2*pi*r;
for (i=0;i<chnum;i++) ans+=dis(p[res[i]],p[res[(i+1)%chnum]]);
printf("%.f\n",ans);
}
return 0;
}
POJ1696【基础】
题目大意:
给出一张图上 n(1<=n<=50)个点的坐标(xi,yi),xi,yi 均为正整数。记这 n
个点中,拥有最小 y 的点为 min,一开始从点(0,ymin)开始走向点 min,然后可
以随意选择径直走去另外的点,但必须满足一下 3 个条件:
1:只能向左(逆时针)转向。
2:走过的路径为留下一条红色的轨迹。
3:不能越过这条红色的轨迹。
问最多能到达几个点,并且按到达的顺序输出各个点的标号。
输入:
第一行有一个整数 T,表示有 T 组测试数据。
每一组测试数据第一行有一个整数 n。接下来 n 行每一行有三个整数,分别表
示一个点的编号和 x、y 坐标。
输出:
每一组测试数据输出一行,包含若干个空格分隔的整数,即依次经过的各点的
序号。
题解:
这题和 Graham 扫描法的思想是一样的,每次选择左转角度最小的一个点作为下一个点,有多个点在同一条直线上则先选距离当前点最近的那个点,那么一定可以遍历所有的点。
#include<cstdio>
#include<cmath>
using namespace std;
#define eps 1e-8
#define MaxN 51
struct Point
{
double x,y;
int id,order;
Point(double a=0,double b=0){x=a;y=b;}
};
/*
得到sp-op和ep-op的叉积
>0时ep在opsp的逆时针方向,<0时顺时针,=0时共线
*/
double Cross(Point &sp,Point &ep,Point &op)
{
return (sp.x-op.x)*(ep.y-op.y)-(ep.x-op.x)*(sp.y-op.y);
}
double dist(Point &p1,Point &p2)
{
double a=p1.x-p2.x;
double b=p1.y-p2.y;
return sqrt(a*a+b*b);
}
Point p[MaxN];
int n;
void DFS(int prev,int depth)//深度搜索。
{
int i,min=0;
double w;
if (depth==n) return ;
while (p[min].order!=-1) min++; //选一个还没用过的点作为当前解
for (i=min+1;i<n;i++)
{
if (p[i].order!=-1) continue;
w=Cross(p[min],p[i],p[prev]);
if (w<-eps) min=i; //如果新的点在当前点的顺时针方向,则用新的点,否则要距离更短才用
else if (abs(w)<eps && dist(p[prev],p[i])<dist(p[prev],p[min])) min=i;
}
p[min].order=depth;
DFS(min,depth+1);
}
void QSort(int l,int r)
{
int i=l,j=r,mid;
Point swap;
mid=p[(i+j)/2].order;
while (i<=j)
{
while (p[i].order<mid) i++;
while (p[j].order>mid) j--;
if (i<=j)
{
swap=p[i];
p[i]=p[j];
p[j]=swap;
i++;j--;
}
}
if (i<r) QSort(i,r);
if (l<j) QSort(l,j);
}
int main()
{
int i,min,t;
freopen("poj1696.txt","r",stdin);
freopen("poj1696ans.txt","w",stdout);
scanf("%d",&t);
while (t--)
{
scanf("%d",&n);
min=0;
for (i=0;i<n;i++)
{
scanf("%d%lf%lf",&p[i].id,&p[i].x,&p[i].y);
p[i].order=-1;
if (p[i].y<p[min].y) min=i;//min记录纵坐标最小的点的编号。
}
p[min].order=0;
DFS(min,1);
QSort(0,n-1);
printf("%d",n);
for (i=0;i<n;i++) printf(" %d",p[i].id);
printf("\n");
}
return 0;
}
POJ1264【基础】
题目大意:
有 k(1<=k<=30)个国家,它们的领土互不重叠。给出每个国家边境线和国境内
上的 N 个(1<=N<=100)点,可以确定一个国家的国土范围。现在有若干枚电磁
脉冲炸弹被投掷下来,只要炸弹在某个国家的国土内或者国境线边上爆炸了的
话,该国会停电。求最后总的停电面积有多大。
输入:
有若干个国家。每个国家第一行有一个整数 N,N=-1 时国家数据输出完毕。接
下来有 N 行,每一行有两个空格分隔的数字,给出了国土内一个点的 x y 坐
标。国家输入完毕后有若干行,每一行包含一个坐标(格式同上),表示一颗
炸弹的弹着点。
输出:
一行,一个两位小数,表示总的停电面积。
#include<stdio.h>
#include<math.h>
#include<string.h>
#define eps 1e-7
#define MaxN 1001
#define pi 3.1415926535
struct Point
{
double x,y;
Point(double a=0,double b=0){x=a;y=b;}
};
typedef Point Vector;
Vector operator + (Point a,Point b)
{
return Vector(a.x+b.x,a.y+b.y);
}
Vector operator - (Point a,Point b)
{
return Vector(a.x-b.x,a.y-b.y);
}
Vector operator * (Point a,double k)
{
return Vector(a.x*k,a.y*k);
}
Vector operator / (Point a,double k)
{
return Vector(a.x/k,a.y/k);
}
/* 2向量求叉积,同时也可以求面积 */
inline double Cross(Vector a,Vector b)
{
return a.x*b.y-b.x*a.y;
}
inline double dis(Point &a,Point &b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
/*------Graham求凸包----*/
/*
得到sp-op和ep-op的叉积
>0时ep在opsp的逆时针方向,<0时顺时针,=0时共线
*/
inline double Cross(Point sp,Point ep,Point op)
{
return (sp.x-op.x)*(ep.y-op.y)-(ep.x-op.x)*(sp.y-op.y);
}
inline int cmp(Point &a,Point &b)
{
if (a.y==b.y) return (a.x<b.x);
return (a.y<b.y);
}
//采用eps的精度判断大/小于零
inline int epssgn(double x)
{
if (fabs(x)<eps) return 0;
else return x<0?-1:1;
}
//对所有的点进行一次排序
void QSort(Point p[],int l,int r)
{
int i=l,j=r;
Point mid=p[(l+r)/2],swap;
while (i<=j)
{
while (cmp(p[i],mid)) i++;
while (cmp(mid,p[j])) j--;
if (i<=j)
{
swap=p[i];
p[i]=p[j];
p[j]=swap;
i++;j--;
}
}
if (i<r) QSort(p,i,r);
if (l<j) QSort(p,l,j);
}
//n为原图的点数,p[]为原图的点(0~n-1),top为凸包点的数量(0~top-1),res[]为凸包点的下标,。
int Graham(Point p[],int n,int res[])
{
int i,len,top;
top=1;
QSort(p,0,n-1);
for (i=0;i<3;i++) res[i]=i;
for (i=2;i<n;i++)
{
while (top && epssgn(Cross(p[i],p[res[top]],p[res[top-1]]))>=0) top--;
res[++top]=i;
}
len=top;
res[++top]=n-2;
for (i=n-3;i>=0;i--)
{
while (top!=len && epssgn(Cross(p[i], p[res[top]], p[res[top-1]]))>=0) top--;
res[++top]=i;
}
return top;
}
//判断点x是否在凸包p[res[1~chnum]]中,返回1则为在内部或边界上
int PointInConvexHull(Point p[],int res[],int chnum,Point x)
{
//找一个凸包内的点
Point g=(p[res[0]]+p[res[chnum/3]]+p[res[2*chnum/3]])/3.0;
int l=0,r=chnum,mid;
//二分凸包
while (l+1<r)
{
mid=(l+r)/2;
if (epssgn(Cross(p[res[l]]-g,p[res[mid]]-g))>0)
{
if (epssgn(Cross(p[res[l]]-g,x-g))>=0
&& epssgn(Cross(p[res[mid]]-g,x-g))<0) r=mid;
else l=mid;
}
else
{
if (epssgn(Cross(p[res[l]]-g,x-g))<0
&& epssgn(Cross(p[res[mid]]-g,x-g))>=0) l=mid;
else r=mid;
}
}
r%=chnum;
if (epssgn(Cross(p[res[r]]-x,p[res[l]]-x))==-1) return 1; else return 0;
}
/*------Graham求凸包----*/
struct ConvexHull
{
Point p[MaxN];
int res[MaxN];
int chnum;
double area;
}kingdom[30];
int main()
{
int n,i,k=0,target;
int vis[MaxN];
Point bomb;
double ans;
freopen("poj1264.txt","r",stdin);
freopen("poj1264ans.txt","w",stdout);
while (scanf("%d",&n)!=EOF)
{
if (n==-1) break;
for (i=0;i<n;i++) scanf("%lf%lf",&kingdom[k].p[i].x,&kingdom[k].p[i].y);
kingdom[k].chnum=Graham(kingdom[k].p,n,kingdom[k].res);
kingdom[k].area=0.0;
for (i=1;i<kingdom[k].chnum-1;i++)
kingdom[k].area+=Cross(kingdom[k].p[kingdom[k].res[i]]
-kingdom[k].p[kingdom[k].res[0]],
kingdom[k].p[kingdom[k].res[i+1]]
-kingdom[k].p[kingdom[k].res[0]]);
kingdom[k].area/=2;
k++;
}
memset(vis,0,sizeof(vis));
ans=0;
kingdom[k].area=0;
while (scanf("%lf%lf",&bomb.x,&bomb.y)!=EOF)
{
for (target=0;target<k;target++)
if (PointInConvexHull(kingdom[target].p,kingdom[target].res,
kingdom[target].chnum,bomb)) break;
if (!vis[target]) ans+=kingdom[target].area;
vis[target]=1;
}
printf("%.2f\n",ans);
return 0;
}
凸包唯一性:
POJ1228【中等】
题目大意:
原本有一个凸多边形,现在我们不知道它的边,它的一部分点也有可能消失
了,问用剩下的 N 个(1<=N<=1000)点能不能确定那个多边形
输入:
第一行有一个整数 t,表示有 t 组测试数据。
每一组测试数据第一行有一个整数 n。接下来有 n 行,每一行两个空格分开的
整数,表示一个点的坐标。
输出:
每一组测试数据输出一行,如果能则输出一行“YES”,否则输出一行“NO”。
题解:
这题要判断的是凸包的唯一性。这要求一个凸包的任何一条边有三个或以上的点,包括顶点。因为这种情况下,任意加一个点进去,都会产生矛盾所以我们先求出凸包,然后枚举每一个点,看看这个点是在凸包的哪一条边上,记录下来,最后看看有没有边没有多余的点即可
#include<stdio.h>
#include<math.h>
#include<string.h>
#define eps 1e-7
#define MaxN 1001
#define pi 3.1415926535
struct Point
{
double x,y;
Point(double a=0,double b=0){x=a;y=b;}
};
typedef Point Vector;
Vector operator - (Point a,Point b)
{
return Vector(a.x-b.x,a.y-b.y);
}
/* 2向量求叉积,同时也可以求面积 */
inline double Cross(Vector a,Vector b)
{
return a.x*b.y-b.x*a.y;
}
inline double dis(Point &a,Point &b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
/*------Graham求凸包----*/
/*
得到sp-op和ep-op的叉积
>0时ep在opsp的逆时针方向,<0时顺时针,=0时共线
*/
inline double Cross(Point sp,Point ep,Point op)
{
return (sp.x-op.x)*(ep.y-op.y)-(ep.x-op.x)*(sp.y-op.y);
}
inline int cmp(Point &a,Point &b)
{
if (a.y==b.y) return (a.x<b.x);
return (a.y<b.y);
}
//采用eps的精度判断大/小于零
inline int epssgn(double x)
{
if (fabs(x)<eps) return 0;
else return x<0?-1:1;
}
//对所有的点进行一次排序
void QSort(Point p[],int l,int r)
{
int i=l,j=r;
Point mid=p[(l+r)/2],swap;
while (i<=j)
{
while (cmp(p[i],mid)) i++;
while (cmp(mid,p[j])) j--;
if (i<=j)
{
swap=p[i];
p[i]=p[j];
p[j]=swap;
i++;j--;
}
}
if (i<r) QSort(p,i,r);
if (l<j) QSort(p,l,j);
}
//n为原图的点数,p[]为原图的点(0~n-1),top为凸包点的数量(0~top-1),res[]为凸包点的下标,。
int Graham(Point p[],int n,int res[])
{
int i,len,top;
top=1;
QSort(p,0,n-1);
for (i=0;i<3;i++) res[i]=i;
for (i=2;i<n;i++)
{
while (top && epssgn(Cross(p[i],p[res[top]],p[res[top-1]]))>=0) top--;
res[++top]=i;
}
len=top;
res[++top]=n-2;
for (i=n-3;i>=0;i--)
{
while (top!=len && epssgn(Cross(p[i], p[res[top]], p[res[top-1]]))>=0) top--;
res[++top]=i;
}
return top;
}
/*------Graham求凸包----*/
Point p[MaxN];
int res[MaxN];
int vis[MaxN];
int chnum;
inline double Dot(Vector a,Vector b)
{
return a.x*b.x+a.y*b.y;
}
//点在线段上
inline int PointOnSegment(Point p,Point s,Point e)
{
return (epssgn(Cross(s-p,e-p)) == 0 && epssgn(Dot(s-p,e-p))<0);
}
int OnCHEdge(int u)
{
int i;
for (i=0;i<chnum;i++) if (PointOnSegment(p[u],p[res[i]],p[res[(i+1)%chnum]])) break;
return i;
}
int main()
{
int n,i,j,t,flag;
freopen("poj1228.txt","r",stdin);
freopen("poj1228ans.txt","w",stdout);
scanf("%d",&t);
while (t--)
{
scanf("%d",&n);
for (i=0;i<n;i++) scanf("%lf%lf",&p[i].x,&p[i].y);
if (n<6) //小于6个点一定不可以
{
printf("NO\n");
continue;
}
chnum=Graham(p,n,res);
if (chnum==2) //全部在一条直线上,不可以
{
printf("NO\n");
continue;
}
memset(vis,0,sizeof(vis));
for (i=0;i<n;i++)
{
flag=0;
for (j=0;j<chnum;j++) if (i==res[j])
{
flag=1;
break;
}
if (flag) continue; //i是凸包顶点,跳过
vis[OnCHEdge(i)]=1; //i点对应的凸包边是安全的
}
flag=1;
for (i=0;i<chnum;i++) if (!vis[i])
{
flag=0;
break;
}
if (flag) printf("YES\n"); else printf("NO\n");
}
return 0;
}
POJ1584【中等】
题目大意:
给出一个圆的半径和圆心以及 n(1<=n<=100)个点,判断这 n 个点是否能组成
一个凸包。如果是,求这个圆是否在凸包内。
输入:
有若干组测试数据。每一组测试数据第一行先有一个整数 N,当 N<3 时测试数
据结束,否则本行还有三个空格分隔的小数,分别表示圆的半径和圆心的 x y
坐标。接下来有 n 行,每一行有两个空格分隔的小数 x y,表示一个点的坐
标。
输出:
每一组测试数据输出一行。如果不能组成凸包,输出“HOLE IS ILL-
FORMED”,否则如果圆不在凸包内则输出“PEG WILL NOT FIT”。如果能组成
凸包且圆在凸包内,输出“PEG WILL FIT”。
题解:
判断能否组成凸包这个我一开始直接求了一次凸包然后比较点数,不过这么做
(在我的求凸包模板上)是错的,因为会有若干点在同一条直线上。因此我们
只要将所有的点按顺序扫一次,判断是否都是像顺时针或者逆时针方向偏转即
可判断能否组成凸包。如果可以,调用模板判断圆心是否在凸包内。如果在的
话,枚举凸包每一条边求其和圆心的距离,如果大于半径即不可。
#include<stdio.h>
#include<math.h>
#define eps 1e-8
#define MaxN 201
//二维点类
struct Point
{
double x,y;
Point(double a=0,double b=0){x=a;y=b;}
};
typedef Point Vector;
Vector operator + (Point a,Point b)
{
return Vector(a.x+b.x,a.y+b.y);
}
Vector operator - (Point a,Point b)
{
return Vector(a.x-b.x,a.y-b.y);
}
Vector operator * (Point a,double k)
{
return Vector(a.x*k,a.y*k);
}
Vector operator / (Point a,double k)
{
return Vector(a.x/k,a.y/k);
}
//得到sp-op和ep-op的叉积
//>0时ep在opsp的逆时针方向,<0时顺时针,=0时共线
double Cross(Point &sp, Point &ep, Point &op)
{
return (sp.x-op.x)*(ep.y-op.y)-(ep.x-op.x)*(sp.y-op.y);
}
//两向量求叉积,求三角形面积需要除以2
double Cross(Vector a,Vector b)
{
return a.x*b.y-b.x*a.y;
}
//两向量求点积
double Dot(Vector a,Vector b)
{
return a.x*b.x+a.y*b.y;
}
//采用eps的精度判断大/小于零
int epssgn(double x)
{
if (fabs(x)<eps) return 0;
else return x<0?-1:1;
}
//求两点之间的直线距离
double dis(Point a,Point b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
//求点C到线段AB的距离
double PointToSegDist(Point A,Point B,Point C)
{
if (dis(A,B)<eps) return dis(B,C);
if (epssgn(Dot(B-A,C-A))<0) return dis(A,C);
if (epssgn(Dot(A-B,C-B))<0) return dis(B,C);
return fabs(Cross(B-A,C-A))/dis(B,A);
}
//判断点x是否在凸包p[res[1~chnum]]中,返回1则为在内部或边界上
int PointInConvexHull(Point p[],int res[],int chnum,Point x)
{
//找一个凸包内的点
Point g=(p[res[0]]+p[res[chnum/3]]+p[res[2*chnum/3]])/3.0;
int l=0,r=chnum,mid;
//二分凸包
while (l+1<r)
{
mid=(l+r)/2;
if (epssgn(Cross(p[res[l]]-g,p[res[mid]]-g))>0)
{
if (epssgn(Cross(p[res[l]]-g,x-g))>=0
&& epssgn(Cross(p[res[mid]]-g,x-g))<0) r=mid;
else l=mid;
}
else
{
if (epssgn(Cross(p[res[l]]-g,x-g))<0
&& epssgn(Cross(p[res[mid]]-g,x-g))>=0) l=mid;
else r=mid;
}
}
r%=chnum;
if (epssgn(Cross(p[res[r]]-x,p[res[l]]-x))==-1) return 1; else return 0;
}
Point p[MaxN];
int res[MaxN];
int chnum;
struct Circle
{
Point center;
double r;
}cir;
int main()
{
int n,i,j,k,flag;
double direct,tmp;
freopen("poj1584.txt","r",stdin);
freopen("poj1584ans.txt","w",stdout);
while (scanf("%d",&n) && n>=3)
{
scanf("%lf%lf%lf",&cir.r,&cir.center.x,&cir.center.y);
for (i=0;i<n;i++) scanf("%lf%lf",&p[i].x,&p[i].y);
for (i=0;i<n-2;i++) //先确定一开始的方向是顺时针还是逆时针
{
direct=Cross(p[i+1],p[i+2],p[i]);
if (epssgn(direct)!=0) break;
}
flag=1;
for (i;i<n-2;i++) //继续向后比较,如果有反向的那么就退出
{
tmp=Cross(p[i+1],p[i+2],p[i]);
if (epssgn(tmp*direct)<0)
{
flag=0;
break;
}
}
if (!flag)
{
printf("HOLE IS ILL-FORMED\n");
continue;
}
//如果direct>0表示是逆时针点集,否则是顺时针,重新构筑一下方便调用函数
if (direct>0) for (i=0;i<n;i++) res[i]=i;
else for (i=0;i<n;i++) res[i]=n-i-1;
chnum=n;
if (!PointInConvexHull(p,res,chnum,cir.center))
{
printf("PEG WILL NOT FIT\n");
continue;
}
//对比圆心和每一条边的距离
flag=1;
for (i=0;i<n;i++) if (cir.r>PointToSegDist(p[i],p[(i+1)%n],cir.center))
{
printf("PEG WILL NOT FIT\n");
flag=0;
break;
}
if (flag) printf("PEG WILL FIT\n");
}
return 0;
}
POJ2187【中等】
题目大意:
有 N(1<=N<=50000)个点,求出任意两点距离的最大值。
输入:
第一行有一个整数 N。接下来有 N 行,每一行有两个整数 x y(绝对值不大于
10000),表示一个点的坐标。
输出:
一行,一个整数,即两点距离的最大值的平方。
#include<stdio.h>
#include<math.h>
#define eps 1e-7
#define MaxN 50001
struct Point
{
double x,y;
Point(double a=0,double b=0){x=a;y=b;}
};
typedef Point Vector;
Vector operator + (Point a,Point b)
{
return Vector(a.x+b.x,a.y+b.y);
}
Vector operator - (Point a,Point b)
{
return Vector(a.x-b.x,a.y-b.y);
}
Vector operator * (Point a,double k)
{
return Vector(a.x*k,a.y*k);
}
Vector operator / (Point a,double k)
{
return Vector(a.x/k,a.y/k);
}
inline double dis(Point &a,Point &b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
/*------Graham求凸包----*/
/*
得到sp-op和ep-op的叉积
>0时ep在opsp的逆时针方向,<0时顺时针,=0时共线
*/
inline double Cross(Point sp,Point ep,Point op)
{
return (sp.x-op.x)*(ep.y-op.y)-(ep.x-op.x)*(sp.y-op.y);
}
inline int cmp(Point &a,Point &b)
{
if (a.y==b.y) return (a.x<b.x);
return (a.y<b.y);
}
//采用eps的精度判断大/小于零
inline int epssgn(double x)
{
if (fabs(x)<eps) return 0;
else return x<0?-1:1;
}
//对所有的点进行一次排序
void QSort(Point p[],int l,int r)
{
int i=l,j=r;
Point mid=p[(l+r)/2],swap;
while (i<=j)
{
while (cmp(p[i],mid)) i++;
while (cmp(mid,p[j])) j--;
if (i<=j)
{
swap=p[i];
p[i]=p[j];
p[j]=swap;
i++;j--;
}
}
if (i<r) QSort(p,i,r);
if (l<j) QSort(p,l,j);
}
//n为原图的点数,p[]为原图的点(0~n-1),top为凸包点的数量(0~top-1),res[]为凸包点的下标,。
int Graham(Point p[],int n,int res[])
{
int i,len,top;
top=1;
QSort(p,0,n-1);
for (i=0;i<3;i++) res[i]=i;
for (i=2;i<n;i++)
{
while (top && epssgn(Cross(p[i],p[res[top]],p[res[top-1]]))>=0) top--;
res[++top]=i;
}
len=top;
res[++top]=n-2;
for (i=n-3;i>=0;i--)
{
while (top!=len && epssgn(Cross(p[i], p[res[top]], p[res[top-1]]))>=0) top--;
res[++top]=i;
}
return top;
}
//两向量求叉积,求三角形面积需要除以2
double Cross(Vector a,Vector b)
{
return a.x*b.y-b.x*a.y;
}
//旋转卡壳求凸包p[res[1~chnum]]的直径,对踵点数量appnum,存储在app[][2]中
double Diameter(Point p[],int res[],int chnum,int app[][2],int &appnum) //AntiPodal Point
{
int i,j;
double ret=0,nowlen;
res[chnum]=res[0];
j=1;
appnum=0;
for (i=0;i<chnum;i++)
{
//卡住i,若向前旋转j能令面积更大的话就一直转一下去
while (Cross(p[res[i]]-p[res[i+1]],p[res[j+1]]-p[res[i+1]])
<Cross(p[res[i]]-p[res[i+1]],p[res[j]] -p[res[i+1]]))
j=(j+1)%chnum;
app[appnum][0]=res[i];
app[appnum][1]=res[j];
appnum++;
nowlen=dis(p[res[i]],p[res[j]]);
if (nowlen>ret) ret=nowlen;
nowlen=dis(p[res[i+1]],p[res[j+1]]);
if (nowlen>ret) ret=nowlen;
}
return ret;
}
/*------Graham求凸包----*/
Point p[MaxN];
int res[MaxN];
int chnum;
int app[MaxN][2];
int main()
{
int i,n,appnum;
double ans;
freopen("poj2187.txt","r",stdin);
freopen("poj2187ans.txt","w",stdout);
while (scanf("%d",&n)!=EOF)
{
for (i=0;i<n;i++) scanf("%lf%lf",&p[i].x,&p[i].y);
if (n<3) printf("%.f\n",dis(p[0],p[1])*dis(p[0],p[1]));
else
{
chnum=Graham(p,n,res);
ans=Diameter(p,res,chnum,app,appnum);
/*旋转卡壳用的调试信息
for (i=0;i<chnum;i++) fprintf(stderr,"%.2f %.2f\n",p[res[i]].x,p[res[i]].y);
for (i=0;i<appnum;i++)
{
fprintf(stderr,"APP : (%.2f %.2f)",p[app[i][0]].x,p[app[i][0]].y);
fprintf(stderr,"-- (%.2f %.2f)",p[app[i][1]].x,p[app[i][1]].y);
fprintf(stderr," \t dist=%.2f\n",dis(p[app[i][0]],p[app[i][1]]));
}
*/
/*暴力写法
ans=0;
for (i=0;i<chnum-1;i++)
for (j=i+1;j<chnum;j++)
ans=max(ans,dis(p[res[i]],p[res[j]]));
*/
printf("%.f\n",ans*ans);
}
}
return 0;
}
POJ2079【中等】
题目大意:
有 N(1<=N<=50000)个点,求出任意三点组成的三角形面积的最大
值。
输入:
有若干组测试数据,每一组第一行有一个整数 N,N=-1 时测试数据结
束。接下来每一行有两个空格分开的整数表示一个点的 x y 坐标。
输出:
每一组测试数据输出一行,包含一个两位的小数,即任意三点组成的
三角形的面积的最大值。
题解:
首先可以肯定,这样的三角形一定三个点全部在凸包上,这是显然
的。然后就是要针对凸包找点了。这里一样采取旋转卡壳的方法:首
先卡住 i,k 两点,让 j 旋转,一直旋转到面积不再增大为止;然后卡
住 i,j,让 k 旋转,一直到面积不再增大为止;然后让 i 步进 1 格,直
到 i 遍历了所有的点。具体实现见代码。
#include<stdio.h>
#include<math.h>
#define eps 1e-7
#define MaxN 50001
struct Point
{
double x,y;
Point(double a=0,double b=0){x=a;y=b;}
};
typedef Point Vector;
Vector operator + (Point a,Point b)
{
return Vector(a.x+b.x,a.y+b.y);
}
Vector operator - (Point a,Point b)
{
return Vector(a.x-b.x,a.y-b.y);
}
Vector operator * (Point a,double k)
{
return Vector(a.x*k,a.y*k);
}
Vector operator / (Point a,double k)
{
return Vector(a.x/k,a.y/k);
}
inline double dis(Point &a,Point &b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
/*------Graham求凸包----*/
/*
得到sp-op和ep-op的叉积
>0时ep在opsp的逆时针方向,<0时顺时针,=0时共线
*/
inline double Cross(Point sp,Point ep,Point op)
{
return (sp.x-op.x)*(ep.y-op.y)-(ep.x-op.x)*(sp.y-op.y);
}
inline int cmp(Point &a,Point &b)
{
if (a.y==b.y) return (a.x<b.x);
return (a.y<b.y);
}
//采用eps的精度判断大/小于零
inline int epssgn(double x)
{
if (fabs(x)<eps) return 0;
else return x<0?-1:1;
}
//对所有的点进行一次排序
void QSort(Point p[],int l,int r)
{
int i=l,j=r;
Point mid=p[(l+r)/2],swap;
while (i<=j)
{
while (cmp(p[i],mid)) i++;
while (cmp(mid,p[j])) j--;
if (i<=j)
{
swap=p[i];
p[i]=p[j];
p[j]=swap;
i++;j--;
}
}
if (i<r) QSort(p,i,r);
if (l<j) QSort(p,l,j);
}
//n为原图的点数,p[]为原图的点(0~n-1),top为凸包点的数量(0~top-1),res[]为凸包点的下标,。
int Graham(Point p[],int n,int res[])
{
int i,len,top;
top=1;
QSort(p,0,n-1);
for (i=0;i<3;i++) res[i]=i;
for (i=2;i<n;i++)
{
while (top && epssgn(Cross(p[i],p[res[top]],p[res[top-1]]))>=0) top--;
res[++top]=i;
}
len=top;
res[++top]=n-2;
for (i=n-3;i>=0;i--)
{
while (top!=len && epssgn(Cross(p[i], p[res[top]], p[res[top-1]]))>=0) top--;
res[++top]=i;
}
return top;
}
/*------Graham求凸包----*/
//两向量求叉积,求三角形面积需要除以2
double Cross(Vector a,Vector b)
{
return a.x*b.y-b.x*a.y;
}
//旋转卡壳求凸包p[res[1~chnum]]内最大面积三角形
double ConvexHullMaxTriangleArea(Point p[],int res[],int chnum)
{
int i,j,k;
double area=0,tmp;
k=2;j=1;
res[chnum]=res[0];
for (i=0;i<chnum;i++)
{
//卡住i,j,若向前旋转k能令面积更大的话就一直转一下去
while (fabs(Cross(p[res[j]]-p[res[i]],p[res[(k+1)%chnum]]-p[res[i]]))
>fabs(Cross(p[res[j]]-p[res[i]],p[res[k]] -p[res[i]])))
k=(k+1)%chnum;
tmp=fabs(Cross(p[res[j]]-p[res[i]],p[res[k]]-p[res[i]]));
if (tmp>area) area=tmp;
//卡住i,k,若向前旋转j能令面积更大的话就一直转一下去
while (fabs(Cross(p[res[(j+1)%chnum]]-p[res[i]],p[res[k]]-p[res[i]]))
>fabs(Cross(p[res[j]]- p[res[i]],p[res[k]]-p[res[i]])))
j=(j+1)%chnum;
tmp=fabs(Cross(p[res[j]]-p[res[i]],p[res[k]]-p[res[i]]));
if (tmp>area) area=tmp;
//j,k转到最大位置,向前转i(i++)
}
return area/2;
}
Point p[MaxN];
int res[MaxN];
int chnum;
int main()
{
int i,n;
double ans;
freopen("poj2079.txt","r",stdin);
freopen("poj2079ans.txt","w",stdout);
while (scanf("%d",&n)!=EOF)
{
if (n==-1) break;
for (i=0;i<n;i++) scanf("%lf%lf",&p[i].x,&p[i].y);
if (n<3) printf("0.00\n");
else
{
chnum=Graham(p,n,res);
ans=ConvexHullMaxTriangleArea(p,res,chnum);
printf("%.2f\n",ans);
}
}
return 0;
}
POJ3608【中等】
题目大意:
一次海啸过后,某个太平洋岛国变成了两个小岛。为了方便两边沟通,要建一
座桥,并且令桥的长度最短。给出两个岛上的 n、m 个(1<=n、m<=10000)点,
这 n、m 个点足以确定这两个岛的边界。
输入:
有若干组测试数据。每一组测试数据第一行有两个整数 n 和 m,表示第一个岛
有 n 个点,第二岛有 m 个点。接下来有 n+m 行,每一行有两个空格分隔的小
数,分别表示一个点的坐标,前 n 行是第一个岛的点,第 n+1 到 n+m 个点是第
二个岛上的点。
输出:
每一组测试数据输出一行,包含一个五位小数,即最短的桥的距离。
题解:
这一题还是要用旋转卡壳法来求两个凸包间的最小距离。首先自然是要先求凸
包的。然后旋转卡壳,步进的凸包(被卡住的凸包)p 选一个 y 坐标最小的
点,旋转的凸包 q 选一个 y 坐标最大的点,然后 q 上的点一直转到两线段间距
离不再减小为止。然后 p 点步进。注意需要做两次这种操作,即卡住 p 转 q 和
卡住 q 转 p,这样才不会有疏漏。
此题在上一张其他计算几何中最后一个例题有二分的方法求点集最近的点
#include<stdio.h>
#include<math.h>
#define eps 1e-7
#define MaxN 10001
struct Point
{
double x,y;
Point(double a=0,double b=0){x=a;y=b;}
};
typedef Point Vector;
Vector operator + (Point a,Point b)
{
return Vector(a.x+b.x,a.y+b.y);
}
Vector operator - (Point a,Point b)
{
return Vector(a.x-b.x,a.y-b.y);
}
Vector operator * (Point a,double k)
{
return Vector(a.x*k,a.y*k);
}
Vector operator / (Point a,double k)
{
return Vector(a.x/k,a.y/k);
}
inline double dis(Point &a,Point &b)
{
return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
/*------Graham求凸包----*/
/*
得到sp-op和ep-op的叉积
>0时ep在opsp的逆时针方向,<0时顺时针,=0时共线
*/
inline double Cross(Point sp,Point ep,Point op)
{
return (sp.x-op.x)*(ep.y-op.y)-(ep.x-op.x)*(sp.y-op.y);
}
inline int cmp(Point &a,Point &b)
{
if (a.y==b.y) return (a.x<b.x);
return (a.y<b.y);
}
//采用eps的精度判断大/小于零
inline int epssgn(double x)
{
if (fabs(x)<eps) return 0;
else return x<0?-1:1;
}
//对所有的点进行一次排序
void QSort(Point p[],int l,int r)
{
int i=l,j=r;
Point mid=p[(l+r)/2],swap;
while (i<=j)
{
while (cmp(p[i],mid)) i++;
while (cmp(mid,p[j])) j--;
if (i<=j)
{
swap=p[i];
p[i]=p[j];
p[j]=swap;
i++;j--;
}
}
if (i<r) QSort(p,i,r);
if (l<j) QSort(p,l,j);
}
//n为原图的点数,p[]为原图的点(0~n-1),top为凸包点的数量(0~top-1),res[]为凸包点的下标,。
int Graham(Point p[],int n,int res[])
{
int i,len,top;
top=1;
QSort(p,0,n-1);
for (i=0;i<3;i++) res[i]=i;
for (i=2;i<n;i++)
{
while (top && epssgn(Cross(p[i],p[res[top]],p[res[top-1]]))>=0) top--;
res[++top]=i;
}
len=top;
res[++top]=n-2;
for (i=n-3;i>=0;i--)
{
while (top!=len && epssgn(Cross(p[i], p[res[top]], p[res[top-1]]))>=0) top--;
res[++top]=i;
}
return top;
}
/*------Graham求凸包----*/
//求向量的模(长度)
double len(Vector a)
{
return sqrt(a.x*a.x+a.y*a.y);
}
//两向量求叉积,求三角形面积需要除以2
double Cross(Vector a,Vector b)
{
return a.x*b.y-b.x*a.y;
}
//两向量求点积
double Dot(Vector a,Vector b)
{
return a.x*b.x+a.y*b.y;
}
Point p0[MaxN],p[MaxN],q[MaxN];
int res[MaxN];
double min(double a,double b)
{
if (a>b) return b; else return a;
}
//求点C到线段AB的距离
double PointToSegDist(Point A,Point B,Point C)
{
if (dis(A,B)<eps) return dis(B,C);
if (epssgn(Dot(B-A,C-A))<0) return dis(A,C);
if (epssgn(Dot(A-B,C-B))<0) return dis(B,C);
return fabs(Cross(B-A,C-A))/dis(B,A);
}
//求线段两端AB到另一线段CD的距离
double TwoSegMinDist(Point A,Point B,Point C,Point D)
{
return min(min(PointToSegDist(A,B,C),PointToSegDist(A,B,D)),
min(PointToSegDist(C,D,A),PointToSegDist(C,D,B)));
}
//求两凸包p、q间的最小距离,注意调用的时候要交换参数位置调用两次
//卡住p(p步进),旋转q,求能得到的最小距离
double TwoConvexHullMinDist(Point P[],Point Q[],int n,int m)
{
int YMinP=0,YMaxQ=0,i;
double tmp,ans=999999999;
for (i=0;i<n;i++) if (P[i].y<P[YMinP].y) YMinP=i;
for (i=0;i<m;i++) if (Q[i].y>Q[YMaxQ].y) YMaxQ=i;
P[n]=P[0];
Q[m]=Q[0];
for (i=0;i<n;i++)
{
//注意,tmp保存的是>比较的结果
while (tmp=Cross(Q[YMaxQ+1]-P[YMinP+1],P[YMinP]-P[YMinP+1])
>Cross(Q[YMaxQ] -P[YMinP+1],P[YMinP]-P[YMinP+1]))
YMaxQ=(YMaxQ+1)%m;
if (tmp<0) ans=min(ans,PointToSegDist(P[YMinP],P[YMinP+1],Q[YMaxQ]));
else ans=min(ans,TwoSegMinDist (P[YMinP],P[YMinP+1],Q[YMaxQ],Q[YMaxQ+1]));
YMinP=(YMinP+1)%n;
}
return ans;
}
int main()
{
int i,n,m;
double ans,tmp;
freopen("poj3608.txt","r",stdin);
freopen("poj3608ans.txt","w",stdout);
while (scanf("%d%d",&n,&m)!=EOF)
{
if (n==0 && m==0) break;
//处理一下重新保存,免得后面写起来太麻烦
for (i=0;i<n;i++) scanf("%lf%lf",&p0[i].x,&p0[i].y);
n=Graham(p0,n,res);
for (i=0;i<n;i++) p[i]=p0[res[i]];
for (i=0;i<m;i++) scanf("%lf%lf",&p0[i].x,&p0[i].y);
m=Graham(p0,m,res);
for (i=0;i<m;i++) q[i]=p0[res[i]];
ans=99999999.0;
//p步进(卡住p),转q
tmp=TwoConvexHullMinDist(p,q,n,m);
if (tmp<ans) ans=tmp;
//q步进(卡住q),转p
tmp=TwoConvexHullMinDist(q,p,m,n);
if (tmp<ans) ans=tmp;
printf("%.5f\n",ans);
}
return 0;
}
POJ1912【难】
题目大意:
给出 N 个点(1<=N<=100000)和 M 个查询(1<=M<=100000),每一个查询给出了
一条直线,问是否所有的点都在直线的一侧。