单调栈求凸包 Andrew算法

凸包的定义是,包住所有给定点的最小凸多边形。他有一个性质是,凸包肯定是所有包住所有点的多边形中周长最小的,因此还可以有一种定义是:包住所有点的,周长最小的多边形

怎么求凸包,首先凸多边形有一个性质:顺着它的边走,肯定是一直在左拐的,例如边上三个相邻点 A , B , C A,B,C A,B,C,那他们形成的向量肯定有 A B × B C > 0 AB×BC>0 AB×BC>0,这里就是利用矢量叉乘判断,叉乘大于零说明左拐的度数小于 180 180 180,也就是左拐而没有右拐。

那么利用这个性质,我们可以在每次加入一个点的时候,看他和凸包里前两个点构成的向量,是不是左拐,如果是右拐,就把凸包里前一个点删掉,然后继续判断,直到是左拐,或凸包中点不足两个

这个过程也可以用图形来理解,如下图,左边三个点显然出现右拐了,那么就把中间那个点删掉了。这是因为出现右拐,这个地方就变成凹多边形了,但是我们在求的是个凸多边形,所以要把这个凹进去的点删掉
在这里插入图片描述
具体实现上,保存的都是满足条件的点,每次把栈顶的不满足要求的点删掉,直到合法,这其实是类似单调栈的方式。实际上我们也确实是在维护一个单调的量:边所在直线的斜率。注意到在上半凸包和下半凸包里,边斜率的变化都是单调的,所以我们也就是要维护一个斜率的单调栈。这里需要注意,斜率单调只对一半的凸包是成立的,所以上下两半我们要分开求。

此外实现的时候,我们需要按x第一关键字,y第二关键字排序,再跑单调栈

void solve() {
    int n;
    cin>>n;
    vector<vector<db>>a;
    rep(i,1,n){
		db x,y;
		cin>>x>>y;
		a.push_back({x,y});
	}
	sort(a.begin(),a.end(),[&](vector<db>&x,vector<db>&y){
		if(x[0]==y[0])return x[1]<y[1];
		return x[0]<y[0];
	});
	stack<int>s;
	auto cross=[&](vector<db>x,vector<db>y)->db{
		return x[0]*y[1]-x[1]*y[0];	
	};
	auto check=[&](int x)->bool{
		auto t1=s.top();
		s.pop();
		auto t2=s.top();
		s.push(t1);

		return cross({a[t1][0]-a[t2][0],a[t1][1]-a[t2][1]},{a[x][0]-a[t1][0],a[x][1]-a[t1][1]})<0;
	};
	
	rep(i,0,n-1){
		while(s.size()>=2&&check(i)){
			s.pop();
		}
		s.push(i);
	}
	
	vi res,res1;
	while(s.size()){
		res.push_back(s.top());
		s.pop();
	}
	reverse(res.begin(),res.end());
	
	rep1(i,n-1,0){
		while(s.size()>=2&&check(i)){
			int x=s.top();
			s.pop();
		}
		s.push(i);
	}
	while(s.size()){
		res1.push_back(s.top());
		s.pop();
	}
	reverse(res1.begin(),res1.end());
	for(int x:res1){
		res.push_back(x);
	}
	db ans=0;
	auto dis=[&](vector<db>x,vector<db>y)->db{
		return sqrt((x[0]-y[0])*(x[0]-y[0])+(x[1]-y[1])*(x[1]-y[1]));
	};
	int m=res.size();
	rep(i,1,m-1){
		ans+=dis(a[res[i]],a[res[i-1]]);
	}
	printf("%.2lf",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值