凸包的原理,这里就不多介绍了.
前几天做多校的时候遇到一道共线的凸包问题. 由于自己图样, 直接用极坐标排序的模板上去做,wa了一天.
然后就到处找资料, 看别人代码,看书.
终于知道了,凸包的 极坐标排序 无法解决共线问题.
一般凸包的极坐标排序,都是找到最左下(y首先考虑)的点后放到point[ 0 ],根据 point[ 0 ]->point[ a ] 和 point[ 0 ]->point[ b ] 叉乘的值排序 , 最后排序结果是把point[ 0 ] 看做原点,其他的点按顺序逆时针分部的. 如果 三点共线, 那么, 按到point[ 0 ] 的距离从小到大排序.
排序如图A 为point[ 0 ], B C Q.... P D E 都是根据极坐标排序 先后找到的点.
接下来 来讲下极坐标排序的弊端(详见lrj黑书P395) .
在极坐标摸板里看到第73行中的 while(top>0&&cross(list[stack[top-1]],list[stack[top]],list[i])<=0) top--;
在不要共线的点的情况下
这一行的意义在于 如果stack 里面有两个或者以上的点. 那取stack[top-1]为中心点 . stack[top]为a点, list是正要入栈的点为b点.
以ABC点为例,在没去掉while()里面的=号之前,这个模板,会删去stack[top]点, 也就是B点, 这样可以选着最少的点,最后留下的是AC点
在看末尾对应的PED点, D点因为离A近,所以先入栈,当E点再入栈的时候, D点会出栈. 所以 最后留下的是PEA
可以见得极坐标排序,如果不需要有共线的点存在的话,是可行的.
如果是要把共线的点也进入stack栈的话 , 得去掉while()中的" ="
如果排序后,选离A远的先入栈, 这显然不行的,以这样的入栈顺序, 在判断CBQ的时候会把B点删去.
如果排序后,选离A进的先入栈, 这显然也不行,在判断PDE时,D点会被删去.
所以,在需要存共线点的情况下,极坐标排序是不行的.
所以在要算共线点的题目中,要用水平排序.
这是极坐标模板;
#include
#include
#include
#include
using namespace std;
const int MAXN=1005;//点数 //复杂度nlogn
struct point
{
int x,y;
};
point list[MAXN];
int stack[MAXN],top;
int cross(point p0,point p1,point p2) //计算叉积 p0p1 X p0p2
{
return (p1.x-p0.x)*(p2.y-p0.y)-(p1.y-p0.y)*(p2.x-p0.x);
}
double dis(point p1,point p2) //计算 p1p2的 距离
{
return sqrt((double)(p2.x-p1.x)*(p2.x-p1.x)+(p2.y-p1.y)*(p2.y-p1.y));
}
bool cmp(point p1,point p2) //极角排序函数 , 角度相同则距离小的在前面
{
int tmp=cross(list[0],p1,p2); //逆时针
if(tmp>0) return true;
else if(tmp==0&&dis(list[0],p1)list[i].y) || ((p0.y==list[i].y)&&(p0.x>list[i].x)) )
{
p0.x=list[i].x;
p0.y=list[i].y;
k=i;
}
}
list[k]=list[0];
list[0]=p0;
sort(list+1,list+n,cmp);//极角排序 // 逆时针
}
void graham(int n) // stack 里存的是凸包外围点的下标. [0,top]
{
int i;
if(n==1) {top=0;stack[0]=0;}
if(n==2)
{
top=1;
stack[0]=0;
stack[1]=1;
}
if(n>2)
{
for(i=0;i<=1;i++) stack[i]=i;
top=1;
for(i=2;i0&&cross(list[stack[top-1]],list[stack[top]],list[i])<=0) top--;//大方向 逆时针
top++; //==0时会去掉短的,延长出 更长的线. <0时,以top-1点为中心,连接top点和当前i点. 若转向为顺时针, 那就去掉top点. 这样保证是最外的.
stack[top]=i;
}
}
}
double sumlen()//自己打的画蛇添竹的周长计算函数
{
double sum=0;
for(int i=1;i<=top;i++)
{
sum+=dis(list[stack[i-1]],list[stack[i]]);
}
sum+=dis(list[stack[0]],list[stack[top]]);
return sum;
}
这是水平排序的模板,代码转自http://blog.csdn.net/a601025382s/article/details/11659113
#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn=1010;
const double pi=atan(1.0)*4;
struct node{
int x,y;
}e[maxn],res[maxn];
int m;
int cmp(node a,node b)
{
if(a.x==b.x)return a.y1&&cross(res[m-1],e[i],res[m-2])<0)m--; //有等于就不能有共线,也没有重点
res[m++]=e[i];
}
k=m;
//求得上凸包
for(i=n-2;i>=0;i--)
{
while(m>k&&cross(res[m-1],e[i],res[m-2])<0)m--;
res[m++]=e[i];
}
if(n>1)m--;//起始点重复。
return m;
}
int main()
{
int T,n,l;
scanf("%d",&T);
while(T--)
{
int i,j,k;
scanf("%d",&n);
for(i=0;i