luoguP2742 Graham扫描法求凸包

一个讲的很好的视频:链接

啥是凸包:凸包就是某些点相互连接形成的一个凸多边形,这个凸多边形的内部要包含所有剩下的点,并且这个凸多边形应该是所有符合条件的凸多边形中周长最小的一个。

咋求凸包:首先要找到最左下角那个点,那个点一定在凸包中(画一画好像很好证),然后以这个点为原点建立平面直角坐标系,计算其它点的极角并根据极角进行排序,然后开始从排序后的第一个点开始连线,第一条线一定会连接(前两个点入栈),当连到第二条线的时候就要看那个点在第一条线所在直线的左边还是右边,如果在非右边,则进行连接即让点入栈(相当于扩凸包),如果共线或在左边,则让点出栈直至该点在某条直线的左边或共线,再把该点入栈,如此重复直至首尾相接得到答案。

下面是ac代码:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<string>
#include<stack>
using namespace std;
const double pi = acos(-1.0);
const int maxn = 100005;
struct point{
	double x,y;
	double len;
}ans[maxn],pt[maxn],point_a;//ans中保存最终结果,pt中保存输入的点,point_a表示左下角的起始点 
int num;//记录凸包中点的数量 
double cross(point a,point b,point c)//求向量ab和向量ac的叉积,若结果大于0则向量ab在向量ac的顺时针方向,若结果小于0则向量ab在向量ac的逆时针方向,若结果等于0,则它们共线,但可能同向也可能反向 
{
	return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
double dis(point a,point b)//求两点间的距离 
{
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
void find(int n)//查找位于左下方的起始点 
{
	int temp = 0;
	point a = pt[0];
	for(int i=0;i<n;++i)
	{
		if(pt[i].y<a.y||pt[i].y==a.y&&pt[i].x<a.x)
		{
			a = pt[i];
			temp = i;
		}
	}
	pt[temp] = pt[0];
	pt[0] = a;
	point_a = a;
}
bool cmp(point a,point b)//极角排序
{
	double x = cross(point_a,a,b);
	if(x>0) return true;
	else if(x<0) return false;
	a.len = dis(point_a,a);
	b.len = dis(point_a,b);
	return a.len < b.len;
}
int Graham(int n)
{
	int top=2;//栈指针
	ans[0]=pt[0];
	ans[1]=pt[1];
	ans[2]=pt[2];
	pt[n]=pt[0];//为了能首尾相接 
	for(int i=3;i<=n;++i)
	{
		while(cross(ans[top-1],ans[top],pt[i])<0&&top>1) top--;//若当前点在前一条边的右边并且栈内元素大于2,则元素出栈(为了让凸包往外扩) 
			ans[++top] = pt[i];//入栈 
	}
	return top;//表示凸包中的点数 
}
void init(int n)//找到起始点后极角排序后求凸包 
{
	find(n);//找到起始点
	sort(pt,pt+n,cmp);//极角排序
	num = Graham(n); //求凸包 
}
double perimeter()//求凸包周长
{
	double sum=0;
	for(int i=1;i<num;++i)
	{
		sum+=dis(ans[i-1],ans[i]);
	}
	sum+=dis(ans[num-1],ans[0]);//加上最后一个点和第一个点之间的距离 
	return sum;
}
double area()//计算凸包的面积
{
	double sum=0;
	for(int i=1;i<num-1;++i)
	{
		sum+=cross(ans[0],ans[i],ans[i+1]);
	}
	sum=fabs(sum)/2.0;
	return sum;
}
int main()
{
	int t,n;
	double sum,sum1,sum2;
	//cin>>t;
	t=1;
	while(t--)
	{
		cin>>n;//n代表有几个点要输入 
		for(int i=0;i<n;++i)
		{
			cin>>pt[i].x>>pt[i].y;
		}
		init(n);//求凸包
		sum1=perimeter();//得到凸包的周长 
		sum2=area();//得到凸包的面积
		printf("%.2lf\n",sum1);
		//cout<<num<<" "<<sum1<<" "<<sum2<<endl;//num是凸包中包含的点的个数 
	}
	return 0;
}
/*
10   
5 0   
0 0   
2 0   
6 0   
9 0   
8 0   
1 0   
7 0   
4 0   
3 0

18.00
这是一个是一条线的凸包,只输出了凸包的周长 
*/ 

板子:

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<string>
#include<stack>
using namespace std;
const double pi = acos(-1.0);
const int maxn = 1010;
struct point{
	double x,y;
	double len;
}ans[maxn],pt[maxn],point_a;//ans中保存最终结果,pt中保存输入的点,point_a表示左下角的起始点 
int num;//记录凸包中点的数量 
double cross(point a,point b,point c)//求向量ab和向量ac的叉积,若结果大于0则向量ab在向量ac的顺时针方向,若结果小于0则向量ab在向量ac的逆时针方向,若结果等于0,则它们共线,但可能同向也可能反向 
{
	return (b.x-a.x)*(c.y-a.y)-(b.y-a.y)*(c.x-a.x);
}
double dis(point a,point b)//求两点间的距离 
{
	return sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));
}
void find(int n)//查找位于左下方的起始点 
{
	int temp = 0;
	point a = pt[0];
	for(int i=0;i<n;++i)
	{
		if(pt[i].y<a.y||pt[i].y==a.y&&pt[i].x<a.x)
		{
			a = pt[i];
			temp = i;
		}
	}
	pt[temp] = pt[0];
	pt[0] = a;
	point_a = a;
}
bool cmp(point a,point b)//极角排序
{
	double x = cross(point_a,a,b);
	if(x>0) return true;
	else if(x<0) return false;
	a.len = dis(point_a,a);
	b.len = dis(point_a,b);
	return a.len < b.len;
}
int Graham(int n)
{
	int top=2;//栈指针
	ans[0]=pt[0];
	ans[1]=pt[1];
	ans[2]=pt[2];
	pt[n]=pt[0];//为了能首尾相接 
	for(int i=3;i<=n;++i)
	{
		while(cross(ans[top-1],ans[top],pt[i])<0&&top>1) top--;//若当前点在前一条边的右边或重合(如果题目要求重合的边算在右边的点中那么就要加上=号)并且栈内元素大于2,则元素出栈(为了让凸包往外扩) 
			ans[++top] = pt[i];//入栈 
	}
	return top;//表示凸包中的点数 
}
void init(int n)//找到起始点后极角排序后求凸包 
{
	find(n);//找到起始点
	sort(pt,pt+n,cmp);//极角排序
	num = Graham(n); //求凸包 
}
double perimeter()//求凸包周长
{
	double sum=0;
	for(int i=1;i<num;++i)
	{
		sum+=dis(ans[i-1],ans[i]);
	}
	sum+=dis(ans[num-1],ans[0]);//加上最后一个点和第一个点之间的距离 
	return sum;
}
double area()//计算凸包的面积
{
	double sum=0;
	for(int i=1;i<num-1;++i)
	{
		sum+=cross(ans[0],ans[i],ans[i+1]);
	}
	sum=fabs(sum)/2.0;
	return sum;
}
double getmax()//求直径(最长的那一根)(凸包是一条线的时候好像会出问题)
{
	double re=0;
	int top=num-1;
	if(top==1)//仅有两个点
		return dis(ans[0],ans[1]);
	ans[++top]=ans[0];//把第一个点放到最后
	int j=2;
	for(int i=0;i<top;++i)
	{
		while(cross(ans[i],ans[i+1],ans[j])<cross(ans[i],ans[i+1],ans[j+1]))
			j=(j+1)%top;
		re=max(re,max(dis(ans[i],ans[j]),dis(ans[i+1],ans[j])));
	}
	return re; 
} 
int main()
{
	int t,n;
	double sum,sum1,sum2;
	cin>>t;
	while(t--)
	{
		cin>>n;//n代表有几个点要输入 
		for(int i=0;i<n;++i)
		{
			cin>>pt[i].x>>pt[i].y;
		}
		init(n);//求凸包 
		sum1=perimeter();//得到凸包的周长 
		sum2=area();//得到凸包的面积 
		cout<<num<<" "<<sum1<<" "<<sum2<<endl;//num是凸包中包含的点的个数 
	}
	return 0;
}
/*
10   
5 0   
0 0   
2 0   
6 0   
9 0   
8 0   
1 0   
7 0   
4 0   
3 0

18.00
这是一个是一条线的凸包,只输出了凸包的周长 
*/ 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值