P4557 [JSOI2018]战争 凸包的闵可夫斯基和

https://www.luogu.com.cn/problem/P4557

  • 题目描述很简单,给定两个凸包,我们设为 a , b a,b a,b,现在问给 b b b凸包一个整体偏移,偏移量为 k → \overrightarrow{k} k ,给定 k → = ( x , y ) \overrightarrow{k}=(x,y) k =(x,y),问这两个凸包有没有公共部分
  • 换句话说,就是问给定的 k → \overrightarrow{k} k 是否满足 b + k → = a b+\overrightarrow{k}=a b+k =a,即 k → = a − b \overrightarrow{k}=a-b k =ab

首先介绍一下闵可夫斯基和
定义:两个欧几里得空间点集的和,也称作两个空间的膨胀集,点集 A A A B B B的闵可夫斯基和定义为 A + B = { a + b , a ∈ A , b ∈ B } A+B=\{a+b,a\in A,b\in B\} A+B={a+b,aA,bB}

  • 求取方法是把这两个空间内部所有的向量按照极角排序,然后找到一个起点,按照极角从小到大依次放边,最后构成的形状就是两个空间的闵可夫斯基和

那么这道题我们其实要求的是 a a a − b -b b的闵可夫斯基和,只要 k → \overrightarrow{k} k 终点在两个空间的膨胀集内部,或者边界,就说明会发生冲突,否则不会

  • 什么叫做凸集?凸集的几何意义是如果一个集合任意两个元素连线上的点也在集合内部,那么这个集合就是一个凸集,如下图,左侧是一个凸集但是右侧却不是一个凸集;代数定义是对于 S S S的子集内的所有 x x x y y y,并且在区间 [ 0 , 1 ] [0,1] [0,1]内所有的值 t t t,如果点 ( 1 − t ) x + t y (1-t)x+ty (1t)x+ty也属于 S S S,那么 S S S就是一个凸集
    在这里插入图片描述
  • 那么这里产生了一个疑问,为什么两个凸集的闵可夫斯基和仍然是一个凸集?我们可以进行如下证明,设 A , B A,B A,B是两个凸集,设 C = A + B , α 1 , α 2 ∈ A , β 1 , β 2 ∈ B C=A+B,\alpha_1,\alpha_2\in A,\beta_1,\beta_2\in B C=A+B,α1,α2A,β1,β2B
  • 由于 ( 1 − t ) α 1 + t α 2 ∈ A (1-t)\alpha_1+t\alpha_2\in A (1t)α1+tα2A ( 1 − t ) β 1 + t β 2 ∈ B (1-t)\beta_1+t\beta_2\in B (1t)β1+tβ2B,所以根据凸集和的定义有 ( 1 − t ) α 1 + t α 2 + ( 1 − t ) β 1 + t β 2 ∈ C (1-t)\alpha_1+t\alpha_2+(1-t)\beta_1+t\beta_2\in C (1t)α1+tα2+(1t)β1+tβ2C
  • 所以 ( 1 − t ) ( α 1 + β 1 ) + t ( α 2 + β 2 ) ∈ C (1-t)(\alpha_1+\beta_1)+t(\alpha_2+\beta_2)\in C (1t)(α1+β1)+t(α2+β2)C又因为根据凸集和的定义有 α 1 + β 1 ∈ C , α 2 + β 2 ∈ C \alpha_1+\beta_1\in C,\alpha_2+\beta_2\in C α1+β1C,α2+β2C,这就证明了C是一个凸集

接下来回到这道题,梳理一下思路,我们首先要求出 A A A − B -B B两个凸集的闵可夫斯基和,这个结果是一个凸包,求闵可夫斯基和我们可以使用固定一个初始点然后进行归并,始终把外面的线加进去,这里使用向量叉积来判断线是否在外部, − B -B B的意思就是坐标都取相反数,算出来要再跑一次 A n d r e w Andrew Andrew防止出现共线,然后进行点是否在凸包内部的二分判定,程序如下

#include <bits/stdc++.h>

using namespace std;
#define db double
const db eps = 1e-10;
const int MAXN = 1e5 + 100;
int sgn(db x){
    if(fabs(x) < eps) return 0;
    return x < 0 ? -1 : 1;
}
struct Point{
    db x, y;
    Point(){}
    Point(db x, db y): x(x), y(y){}
    Point operator + (const Point &B)const{
        return Point(x + B.x, y + B.y);
    }
    Point operator - (const Point &B)const{
        return Point(x - B.x, y - B.y);
    }
    bool operator < (const Point &B)const{
        return sgn(x - B.x) < 0 || (sgn(x - B.x) == 0 && sgn(y - B.y) < 0);
    }
    bool operator == (const Point &B)const{
        return sgn(x - B.x) == 0 && sgn(y - B.y) == 0;
    }
}s1[MAXN], s2[MAXN], ch1[MAXN], ch2[MAXN];
Point M[MAXN], ans[MAXN];
typedef Point Vector;
db Cross(Vector A, Vector B){
    return A.x * B.y - A.y * B.x;
}
int Convex_hull(Point *s, Point *ch, int n){
    int v = 0;
    sort(s, s + n);
    n = unique(s, s + n) - s;
    for(int i=0;i<n;i++){
        while(v > 1 && sgn(Cross(ch[v - 1] - ch[v - 2], s[i] - ch[v - 2])) <= 0){
            v -= 1;
        }
        ch[v++] = s[i];
    }
    int j = v;
    for(int i=n-2;i>=0;i--){
        while(v > j && sgn(Cross(ch[v - 1] - ch[v - 2], s[i] - ch[v - 2])) <= 0){
            v -= 1;
        }
        ch[v++] = s[i];
    }
    if(n > 1) v -= 1;
    return v;
}
int check(Point A, Point *ch, int n){
    int l = 0;
    int r = n - 1;
    while(r - l > 1){
        int mid = ((r - l) >> 1) + l;
        db a1 = Cross(ch[mid] - ch[0], A - ch[0]);
        db a2 = Cross(ch[mid + 1] - ch[0], A - ch[0]);
        if(sgn(a1) >= 0 && sgn(a2) <= 0){
            if(sgn(Cross(ch[mid + 1] - ch[mid], A - ch[mid])) >= 0) return 1;
            return 0;
        }else if(sgn(a1) < 0){
            r = mid;
        }else{
            l = mid;
        }
    }
    return 0;
}
int Minkowski_sum(int n, int m){
    int tot;
    for(int i=1;i<n;i++){
        s1[i] = ch1[i] - ch1[i - 1];
    }
    s1[n] = ch1[0] - ch1[n - 1];
    for(int i=1;i<m;i++){
        s2[i] = ch2[i] - ch2[i - 1];
    }
    s2[m] = ch2[0] - ch2[m - 1];
    ans[tot = 0] = s1[0] + s2[0];
    int pt1, pt2;
    pt1 = pt2 = 1;
    while(pt1 <= n && pt2 <= m){
        tot += 1;
        ans[tot] = ans[tot - 1] + (sgn(Cross(s1[pt1], s2[pt2])) >= 0 ? s1[pt1++] : s2[pt2++]);
    }
    while(pt1 <= n){
        tot += 1;
        ans[tot] = ans[tot - 1] + s1[pt1++];
    }
    while(pt2 <= m){
        tot += 1;
        ans[tot] = ans[tot - 1] + s2[pt2++];
    }
    return tot;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    int n, m, q;
    db x, y;
    cin >> n >> m >> q;
    for(int i=0;i<n;i++){
        cin >> s1[i].x >> s1[i].y;
    }
    int a = Convex_hull(s1, ch1, n);
    for(int i=0;i<m;i++){
        cin >> s2[i].x >> s2[i].y;
        s2[i].x = -s2[i].x;
        s2[i].y = -s2[i].y;
    }
    int b = Convex_hull(s2, ch2, m);
    int c = Minkowski_sum(a, b);
    c = Convex_hull(ans, M, c);
    while(q--){
        cin >> x >> y;
        cout << check(Point(x, y), M, c) << '\n';
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Clarence Liu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值