zoukankan      html  css  js  c++  java
  • 凸包(Graham与Andrew算法求凸包)

    经常听到各种巨神说凸包、凸包那凸包究竟是什么呢?

    给你平面上若干个点让你求一个周长最小的凸多边形使得所有点均在其内部或边上。

    如下图绿色即为凸包(应该不用证明吧...)

    现在我们先来思考一个问题:给你这些点的坐标,让你求出其凸包的顶点与周长?

    先抛一道模板题给大家:[USACO5.1]圈奶牛Fencing the Cows

    Gramham扫描法

    我绝对不会告诉你我在写这篇博客时还没有实现过这种方法

    首先这种方法需要我们以一个点为原点建立坐标系,要求这个点一定在凸包上。

    通常我们会选择最下方的一个点,同时在满足其为最下方的前提下是最左边的。

    可(感)以(性)证(理)明(解)这个点一定在凸包上。

    按照上面模板题的样例来看的话新的坐标系即为下图:

    画工不好请见谅

    之后对于每个其它点按与新原点向量与(x)轴夹角从小到大排序。

    遍历每个点判断加上这个点之后哪些点不再属于凸包上的点,同时用一个栈来维护凸包。

    大体思路就介绍的差不多了,下面讲一下细节。

    1. 如何按夹角排序。

    我们发现夹角的范围其实是([0,pi]),那么直接按照(cos)值从大到小排序即为夹角从小到大排序。

    1. 如何判断是不是凸包上的点。

    假设现在凸包的候选集合只有两个点(至少会有一个点),则直接加进来即可。

    如果超过两个,则需判断是呈凹状还是凸状。

    左图中被红圈圈出的点既要退出凸包候选集合。其实如果是平的也可以踢出去。(所以说平的凹的我们都不要,我们只要凸的!车车,好快的车车!)

    为什么不要?这种问题老绅士们还问,三角形两边之和大于第三边了解一下。

    1. 如何判断凹凸

    有个东西叫做向量叉积,如果叉积小于(0)则说明是凹的。((b)(a)向右拐)

    然后不知道泥萌会不会写代码了呢?反正我是不太会的。

    花了快10min写了一个竟然一遍过了。

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 100010;
    struct Point{
    	double x, y;
    }a[N];
    int n, sy;
    int stk[N], top;
    double ans;
    double dis(Point p, Point q) {
    	return sqrt((p.x - q.x) * (p.x - q.x) + (p.y - q.y) * (p.y - q.y));
    }
    double Cos(Point p) {
    	return (p.x - a[1].x) / dis(p, a[1]);
    }
    double calc(Point p, Point q) {
    	return p.x * q.y - p.y * q.x;
    }
    Point sub(Point p, Point q) {
    	return Point{q.x - p.x, q.y - p.y};
    }
    bool cmp(Point p, Point q) {
    	return Cos(p) > Cos(q);
    }
    int main() {
    	scanf("%d", &n);
    	for (int i = 1; i <= n; i++) {
    		scanf("%lf%lf", &a[i].x, &a[i].y);
    		if (sy == 0 || a[i].y < a[sy].y || (a[i].y == a[sy].y && a[i].x < a[sy].x)) {
    			sy = i;
    		}
    	}
    	swap(a[1], a[sy]);
    	sort(a + 2, a + n + 1, cmp);
    	stk[top = 1] = 1;
    	for (int i = 2; i <= n; i++) {
    		while (top > 2 && calc(sub(a[stk[top - 1]], a[stk[top]]), sub(a[stk[top]], a[i])) <= 0) top--;
    		stk[++top] = i;
    	}
    	ans = dis(a[stk[1]], a[stk[top]]);
    	while (top > 1) {
    		ans += dis(a[stk[top]], a[stk[top - 1]]);
    		top--;
    	}
    	printf("%.2lf", ans);
    	return 0;
    }
    

    Andrew算法

    Andrew算法其实一个维护上下凸壳的算法。

    这里按照(x)的大小从小到大排序,如果一样再按(y)

    然后按照上面同样的算法求一遍凸壳。然后你就发现你只求了一半啊,于是需要从后往前再求一遍把剩下一半求出来。

    #include <bits/stdc++.h>
    using namespace std;
    const int N = 10010;
    struct node{
    	double x, y;
    }pos[N];
    node stk[N];
    int top;
    int n;
    double ans;
    bool cmp(node a, node b) {
    	if (a.x != b.x) return a.x < b.x;
    	return a.y < b.y; 
    }
    node cal1(node a, node b) {
    	a.x -= b.x;
    	a.y -= b.y;
    	return a;
    }
    double cal2(node a, node b) {
    	return a.x * b.y - a.y * b.x;
    }
    double dis(node a, node b) {
    	return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
    }
    int main() {
    	cin >> n;
    	for (int i = 1; i <= n; i++) {
    		cin >> pos[i].x >> pos[i].y; 
    	}
    	sort(pos + 1, pos + n + 1, cmp);
    	for (int i = 1; i <= n; i++) {
    		stk[++top] = pos[i];
    		while (top > 2 && cal2(cal1(stk[top - 1], stk[top - 2]), cal1(stk[top], stk[top - 1])) <= 0) stk[top - 1] = stk[top], top--;
    	}
    	while (top > 1) ans += dis(stk[top], stk[top - 1]), top--;
    	top = 0;
    	for (int i = n; i >= 1; i--) {
    		stk[++top] = pos[i];
    		while (top >= 3 && cal2(cal1(stk[top - 1], stk[top - 2]), cal1(stk[top], stk[top - 1])) <= 0) stk[top - 1] = stk[top], top--;
    	}
    	while (top > 1) ans += dis(stk[top], stk[top - 1]), top--;
    	printf("%.2lf", ans);
    	return 0;
    }
    
  • 相关阅读:
    缺少一个=出现的问题
    快速排序+归并排序
    ACwing简单题(14)
    浅谈#ifndef
    fstream 使用详解
    _stat函数的使用
    关于文件结构体的使用
    new的使用
    ACwing13题目
    ACwing13题
  • 原文地址:https://www.cnblogs.com/zcr-blog/p/13210874.html
Copyright © 2011-2022 走看看