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

凸包与闵可夫斯基和的计算与应用
该博客探讨了凸包的概念及其性质,重点介绍了如何通过计算两个凸包的闵可夫斯基和来判断它们是否存在交集。文章详细阐述了闵可夫斯基和的定义,以及如何利用极角排序和向量叉积进行凸包的构建。同时,解释了凸集的数学定义,并证明了两个凸集的闵可夫斯基和仍为凸集。最后,给出了一个C++程序,用于解决给定偏移量下两个凸包是否有交集的问题,涉及点在凸包内的二分查找判断等算法细节。

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;
}
引用中未提及P2143 [JSOI2010]巨额奖金的相关信息,但有BZOJ 1016 JSOI 2008巨额奖金的问题描述。该问题是在一个有n个区、m条干道的城市规划交通枢纽,要将部分干道改进为新型干道,使任何两个区可通过新型干道直接或间接连接,已知每条干道改进费用,求建设新型干道总费用最小的方案数,输出方案总数除以31011的模。 解题思路如下: 1. 先使用Kruskal算法求出最小生成树的权值,同时记录每种权值的边在最小生成树中使用的数量。 2. 对于每种权值的边,通过枚举其所有可能的组合情况,判断这些组合能否构成满足条件的部分生成树,统计满足条件的组合数。 3. 根据乘法原理,将每种权值边的组合数相乘,得到最终的方案数,再对31011取模。 以下是代码实现示例(Python 伪代码): ```python MOD = 31011 # 边的类 class Edge: def __init__(self, u, v, w): self.u = u self.v = v self.w = w # 并查集查找操作 def find(parent, x): if parent[x] != x: parent[x] = find(parent, parent[x]) return parent[x] # 并查集合并操作 def union(parent, rank, x, y): root_x = find(parent, x) root_y = find(parent, y) if rank[root_x] < rank[root_y]: parent[root_x] = root_y elif rank[root_x] > rank[root_y]: parent[root_y] = root_x else: parent[root_y] = root_x rank[root_x] += 1 # Kruskal算法求最小生成树 def kruskal(edges, n): edges.sort(key=lambda x: x.w) parent = [i for i in range(n + 1)] rank = [0] * (n + 1) mst_edges = [] total_weight = 0 for edge in edges: u = edge.u v = edge.v w = edge.w root_u = find(parent, u) root_v = find(parent, v) if root_u != root_v: union(parent, rank, u, v) mst_edges.append(edge) total_weight += w return mst_edges, total_weight # 统计每种权值边的方案数 def count_schemes(edges, n): mst_edges, _ = kruskal(edges, n) weight_count = {} for edge in mst_edges: if edge.w not in weight_count: weight_count[edge.w] = 0 weight_count[edge.w] += 1 total_schemes = 1 for weight, count in weight_count.items(): # 这里需要具体实现枚举组合并判断的逻辑 # 由于代码复杂,此处省略具体实现 # 假设 valid_combinations 是该权值边的有效组合数 valid_combinations = 1 total_schemes = (total_schemes * valid_combinations) % MOD return total_schemes # 主函数 def main(): # 读取输入 n, m = map(int, input().split()) edges = [] for _ in range(m): u, v, w = map(int, input().split()) edges.append(Edge(u, v, w)) # 计算方案数 schemes = count_schemes(edges, n) print(schemes) if __name__ == "__main__": main() ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Clarence Liu

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

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

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

打赏作者

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

抵扣说明:

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

余额充值