向量叉积的应用(求多边形面积、凸包问题)

叉积概念

两个向量a(x1,y1),b(x2,y2),叉积:a×b=x1y2-x2y1

叉积的应用:

判断向量位置关系

向量叉积的应用(求多边形面积、凸包问题)_第1张图片

可以用这个性质对点进行角度排序

引伸:

判断两条线段是否相交:
线段如图
向量叉积的应用(求多边形面积、凸包问题)_第2张图片向量叉积的应用(求多边形面积、凸包问题)_第3张图片
当p1p2在p3p4两侧且p3p4在p1p2两侧时相交,或者一个点在另一条线段上也相交
只要通过叉积判断两点关系即可,下面是判断的代码

#include 
using namespace std;

#define esp 1e-10
struct point
{
    double x, y;
    friend point operator-(point a, point b)
    {
        return point{a.x - b.x, a.y - b.y};
    }
    friend point operator+(point a, point b)
    {
        return point{a.x + b.x, a.y + b.y};
    }
    friend double operator*(point a, point b) //重载乘法,实现叉积运算
    {
        return a.x * b.y - b.x * a.y;
    }
};

//判断共线的三点中p0是否在p1p2线段上
bool on_segment(point p0, point p1, point p2)
{
    double dx = (p0.x - p1.x) * (p0.x - p2.x);
    double dy = (p0.y - p1.y) * (p0.y - p2.y);
    if (dx < esp && dy < esp) return true;
    return false;
}

//判断是否相交
bool is_cross(point p1, point p2, point p3, point p4)
{
    //计算叉积
    double d1 = (p1 - p3) * (p1 - p4);
    double d2 = (p2 - p3) * (p2 - p4);
    double d3 = (p3 - p1) * (p3 - p2);
    double d4 = (p4 - p1) * (p4 - p2);
    if (d1 * d2 < 0 && d3 * d4 < 0) return true; //x型相交
    //T型相交
    if (fabs(d1) < esp && on_segment(p1, p3, p4))
        return true;
    else if (fabs(d2) < esp && on_segment(p2, p3, p4))
        return true;
    else if (fabs(d3) < esp && on_segment(p3, p1, p2))
        return true;
    else if (fabs(d4) < esp && on_segment(p4, p1, p2))
        return true;
    return false;
}

int main()
{
    //freopen("in.txt", "r", stdin);
    point a, b, c, d;
    cin >> a.x >> a.y;
    cin >> b.x >> b.y;
    cin >> c.x >> c.y;
    cin >> d.x >> d.y;
    if (is_cross(a, b, c, d))
        cout << "cross";
    else
        cout << "no cross";
    return 0;
}

求多边形面积

三角形面积:S△(A,B,C)=(AB×AC)/2
多边形面积:
向量叉积的应用(求多边形面积、凸包问题)_第4张图片
先将点按照角度进行排序,然后在平面内任选一点O,依次连接多边形两点进行叉积并求和,最后取绝对值(凹多边形和凸多边形均适用,因为叉积有正负之分,相加的时候可以把多算的面积抵消)

凸包问题

问题描述: 在平面上给定一些点,求出能围住所有点的最小多边形周长。
解决方法: 格雷汉姆扫描法
因为三角形中任意两边的和大于第三边,所以最终的多边形一定是凸多边形。
找到y坐标最小的点,从这个点开始将所有的点按逆时针顺序排序,然后开始取点,每取一个点就判断这个点与之前的点连成的边是否是符合凸多边形的,如果不是,将上一个点去除,重复该步骤直至新的边符合凸多边形。
例题:P2742 圈奶牛Fencing the Cows 二维凸包

//有两个样例过不了,能过80%,暂时找不到原因(比如所有的点都在一条线上)
#include 
using namespace std;

struct point
{
    double x, y;
} p[100005];

double cross_product(point &a1, point &a2, point &b1, point &b2) //计算向量叉积x1y2-x2y1
{
    return (a2.x - a1.x) * (b2.y - b1.y) - (b2.x - b1.x) * (a2.y - a1.y);
}

void swap(point &a, point &b)
{
    point temp = a;
    a = b;
    b = temp;
}
double dist(point &p1, point &p2)
{
    return sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y));
}
int cmp(point a, point b) //逆时针顺序排序 va×vb<0
{
    double tmp = cross_product(p[0], a, p[0], b);
    if (tmp > 0)
        return 1;
    if (fabs(tmp) <= 1e-15 && dist(p[0], a) > dist(p[0], b))
        return 1;
    return 0;
}

void get_miny(point *p1, point *p2) //将y坐标最小的点移动至第一个位置,传入参数和sort类似
{
    double miny = 100000;
    point *m;
    for (point *i = p1; i < p2; i++)
    {
        if (i->y < miny)
        {
            miny = i->y;
            m = i;
        }
    }
    point temp = *m;
    *m = *p1;
    *p1 = temp;
}

double graham_scan(point *p1, point *p2) //格雷汉姆扫描法,返回凸包周长(传入的点需要排序好)
{
    vector<point> v;
    double length = 0;
    v.push_back(*p1);
    int end;
    for (point *i = p1 + 1; i != p2; i++)
    {
        end = v.size() - 2;
        //回退步骤
        while (v.size() > 1 && cross_product(v[end], v.back(), v.back(), *i) <1e-10)
        {
            v.pop_back();
            end--;
        }
        v.push_back(*i);
    }
    v.push_back(*p1); //将第一个点再次加入,便于后续计算周长
    for (int i = v.size() - 1; i != 0; i--)
    {
        length += dist(v[i], v[i - 1]);
    }
    return length;
}
int main()
{
    int n;
    cin >> n;
    for (int i = 0; i < n; i++)
        scanf("%lf%lf", &p[i].x, &p[i].y);
    get_miny(p, p + n);
    sort(p + 1, p + n, cmp);
    printf("%.2lf\n", graham_scan(p, p + n));
    return 0;
}

你可能感兴趣的:(经验分享)