voronoi diagram(泰森多边形) 应用 - Good Manners

欢迎关注更多精彩
关注我,学习常用算法与数据结构,一题多解,降维打击。

voronoi 图求解点击前往

题目链接:https://vjudge.net/problem/URAL-1504

题目大意

有一个桌子,形状是圆形。

桌上放着很多蛋糕,每个蛋糕有大小。

人可以坐在桌子边缘的任意位置,坐下后可以拿离自己最近的一块蛋糕,求坐在哪个位置可以拿到尽可能大的蛋糕。

思考&分析

先求出每个蛋糕所在voronoi区域。

查看区域与圆有没有交点。

遍历有交点的蛋糕,选择可以拿到最大蛋糕的交点。

算法细节

1)判断区域与圆有交
可以求出区域所有顶点,判断有没有顶点在圆上或外面。

2)直线与圆求交
在这里插入图片描述
可以利用投影先求到P点,利用直角三角形可知PB’长度
从P点沿AB方向就可以得到B’坐标,同理,反向还有一个交点。

3)特殊情况
只有2个蛋糕或者所有蛋糕在一条线上时,不能进行三角化。

此时,选择最大的蛋糕,作垂线与圆求交点即可。

算法过程

step 1 对边界点进行delaunay三角化
step 2 遍历每个边界点,收集邻边,并按照逆时针排序。
求解出每个邻边的中垂线,分别与边界和其他中垂线求交(邻近的中垂线才有交点)判断是否与圆有交集
step 3 求解与圆的交点,并判断交点是否在voronoi区域内。
step 4 选取可以拿到最大蛋糕的位置点

代码


#include<stdio.h>
#include<cmath>
#include <algorithm>
#include <vector>
#include <list>
#include <cstring>
#include <utility>using namespace std;
const double EPS = 1e-8;const int N = 1e6 + 10;
const int M = 1e6 + 10;int cmp(double d) {if (abs(d) < EPS)return 0;if (d > 0)return 1;return -1;
}class Point {
public:double x, y;int id;Point() {}Point(double a, double b) :x(a), y(b) {}Point(const Point& p) :x(p.x), y(p.y), id(p.id) {}void in() {scanf("%lf %lf", &x, &y);}void out() {printf("%f %f\n", x, y);}double dis() {return sqrt(x * x + y * y);}double dis2() {return x * x + y * y;}Point operator -() const {return Point(-x, -y);}Point operator -(const Point& p) const {return Point(x - p.x, y - p.y);}Point operator +(const Point& p) const {return Point(x + p.x, y + p.y);}Point operator *(double d)const {return Point(x * d, y * d);}Point operator /(double d)const {return Point(x / d, y / d);}void operator -=(Point& p) {x -= p.x;y -= p.y;}void operator +=(Point& p) {x += p.x;y += p.y;}void operator *=(double d) {x *= d;y *= d;}void operator /=(double d) {this ->operator*= (1 / d);}bool operator<(const Point& a) const {return x < a.x || (abs(x - a.x) < EPS && y < a.y);}bool operator==(const Point& a) const {return abs(x - a.x) < EPS && abs(y - a.y) < EPS;}
};// 向量操作double cross(const Point& a, const Point& b) {return a.x * b.y - a.y * b.x;
}double dot(const Point& a, const Point& b) {return a.x * b.x + a.y * b.y;
}class Point3D {
public:double x, y, z;Point3D() {}Point3D(double a, double b, double c) :x(a), y(b), z(c) {}Point3D(const Point3D& p) :x(p.x), y(p.y), z(p.z) {}double dis() {return sqrt(x * x + y * y + z * z);}double dis2() {return x * x + y * y + z * z;}Point3D operator -(const Point3D& p) const {return Point3D(x - p.x, y - p.y, z - p.z);}Point3D operator +(const Point3D& p) const {return Point3D(x + p.x, y + p.y, z + p.z);}Point3D operator *(double d)const {return Point3D(x * d, y * d, z * d);}Point3D operator /(double d)const {return Point3D(x / d, y / d, z / d);}void operator -=(Point3D& p) {x -= p.x;y -= p.y;z -= p.z;}void operator +=(Point3D& p) {x += p.x;y += p.y;z += p.z;}void operator *=(double d) {x *= d;y *= d;z *= d;}void operator /=(double d) {this ->operator*= (1 / d);}
};// 向量操作
Point3D cross(const Point3D& a, const Point3D& b) {return Point3D(a.y * b.z - a.z * b.y, -a.x * b.z + a.z * b.x,a.x * b.y - a.y * b.x);
}double dot(const Point3D& a, const Point3D& b) {return a.x * b.x + a.y * b.y + a.z * b.z;
}class Line {
public:Point front, tail;Line() {}Line(Point a, Point b) :front(a), tail(b) {}
};/*
0 不相交
1 相交
0 平行/重合
*/
int cross(const Line& a, const Line& b) {Point dir1 = a.front - a.tail;Point dir2 = b.front - b.tail;if (cmp(cross(dir1, dir2)) == 0) {return 0;}if (cmp(cross(a.front - b.tail, dir2)) * cmp(cross(a.tail - b.tail, dir2)) >= 0)return 0;if (cmp(cross(b.front - a.tail, dir1)) * cmp(cross(b.tail - a.tail, dir1)) >= 0)return 0;return 1;
}int inCircle(Point p0, Point p1, Point p2, Point p3) {Point d1 = p1 - p0;Point d2 = p2 - p0;if (cross(d1, d2) < 0)return inCircle(p0, p2, p1, p3); // 保证平面法向向上// 构建映射点Point3D lift0(p0.x, p0.y, p0.dis2());Point3D lift1(p1.x, p1.y, p1.dis2());Point3D lift2(p2.x, p2.y, p2.dis2());Point3D lift3(p3.x, p3.y, p3.dis2());Point3D z1(lift1 - lift0), z2(lift2 - lift0);Point3D normal = cross(z1, z2); // 计算平面法向double project = dot(normal, lift3 - lift0); // 计算点到平面距离return cmp(project);
}class EdgeDelaunay {
public:int id;std::list<EdgeDelaunay>::iterator c;EdgeDelaunay(int id = 0) { this->id = id; }
};class Delaunay {
public:std::list<EdgeDelaunay> head[N];  // graphPoint p[N];int n = 0;void init(int psize, Point ps[]) {this->n = psize;memcpy(this->p, ps, sizeof(Point) * n);std::sort(this->p, this->p + n);divide(0, n - 1);}void addEdge(int u, int v) {head[u].push_front(EdgeDelaunay(v));head[v].push_front(EdgeDelaunay(u));head[u].begin()->c = head[v].begin();head[v].begin()->c = head[u].begin();}void divide(int l, int r) {if (r - l <= 1) {  // #point <= 2for (int i = l; i <= r; i++)for (int j = i + 1; j <= r; j++) addEdge(i, j);return;}int mid = (l + r) / 2;divide(l, mid);divide(mid + 1, r);std::list<EdgeDelaunay>::iterator it;int nowl = l, nowr = r;for (int update = 1; update;) {// 查找左边最低线位置update = 0;Point ptL = p[nowl], ptR = p[nowr];for (it = head[nowl].begin(); it != head[nowl].end(); it++) {Point t = p[it->id];double v = cross(ptL - ptR, t - ptR);if (cmp(v) > 0 || (cmp(v) == 0 && (t - ptR).dis() < (ptL - ptR).dis())) {nowl = it->id, update = 1;break;}}if (update) continue;// 查找右边最低线位置for (it = head[nowr].begin(); it != head[nowr].end(); it++) {Point t = p[it->id];double v = cross(ptR - ptL, t - ptL);if (cmp(v) < 0 || (cmp(v) == 0 && (t - ptL).dis() < (ptL - ptR).dis())) {nowr = it->id, update = 1;break;}}}addEdge(nowl, nowr);  // 添加基线for (; true;) {Point ptL = p[nowl], ptR = p[nowr];int ch = -1, side = 0;for (it = head[nowl].begin(); it != head[nowl].end(); it++) {if (cmp(cross(ptR - ptL, p[it->id] - ptL)) <= 0)continue; // 判断夹角是否小于180if (ch == -1 || inCircle(ptL, ptR, p[ch], p[it->id]) < 0) {ch = it->id, side = -1;}}for (it = head[nowr].begin(); it != head[nowr].end(); it++) {if (cmp(cross(p[it->id] - ptR, ptL - ptR)) <= 0) continue;// 判断夹角是否小于180if (ch == -1 || inCircle(ptL, ptR, p[ch], p[it->id]) < 0) {ch = it->id, side = 1;}}if (ch == -1) break;  // 所有线已经加完if (side == -1) {for (it = head[nowl].begin(); it != head[nowl].end();) {// 判断是否相交,边缘不算相交if (cross(Line(ptL, p[it->id]), Line(ptR, p[ch]))) {head[it->id].erase(it->c);head[nowl].erase(it++);}else {it++;}}nowl = ch;addEdge(nowl, nowr);}else {for (it = head[nowr].begin(); it != head[nowr].end();) {// 判断是否相交,边缘不算相交if (cross(Line(ptR, p[it->id]), Line(ptL, p[ch]))) {head[it->id].erase(it->c);head[nowr].erase(it++);}else {it++;}}nowr = ch;addEdge(nowl, nowr);}}}std::vector<std::pair<int, int> > getEdge() {std::vector<std::pair<int, int> > ret;ret.reserve(n);std::list<EdgeDelaunay>::iterator it;for (int i = 0; i < n; i++) {for (it = head[i].begin(); it != head[i].end(); it++) {ret.push_back(std::make_pair(p[i].id, p[it->id].id));}}return ret;}
};/*
点p 到 p+r 表示线段1
点q 到 q+s 表示线段2
线段1 上1点用 p' = p+t*r (0<=t<=1)
线段2 上1点用 q' = q+u*s (0<=u<=1)
让两式相等求交点 p+t*r = q+u*s
两边都叉乘s
(p+t*r)Xs = (q+u*s)Xs
pXs + t*rXs = qXs
t = (q-p)Xs/(rXs)
同理,
u = (p-q)Xr/(sXr) -> u = (q-p)Xr/(rXs)以下分4种情况:
1. 共线,sXr==0 && (q-p)Xr==0, 计算 (q-p)在r上的投影在r长度上的占比t0,
计算(q+s-p)在r上的投影在r长度上的占比t1,查看[t0, t1]是否与范围[0,1]有交集。
如果t0>t1, 则比较[t1, t0]是否与范围[0,1]有交集。
t0 = (q-p)*r/(r*r)
t1 = (q+s-p)*r/(r*r) = t0 + s · r / (r · r)
2. 平行sXr==0 && (q-p)Xr!=0
3. 0<=u<=1 && 0<=t<=1 有交点
4. 其他u, t不在0到范围内,没有交点。
*/
pair<double, double> intersection(const Point& q, const Point& s, const Point& p, const Point& r) {// 计算 (q-p)Xrauto qpr = cross(q - p, r);auto qps = cross(q - p, s);auto rXs = cross(r, s);if (cmp(rXs) == 0)return { -1, -1 }; // 平行或共线// 求解t, u// t = (q-p)Xs/(rXs)auto t = qps / rXs;// u = (q-p)Xr/(rXs)auto u = qpr / rXs;return { u, t };
}Point oiPs[N];
Delaunay de;
Point lowPoint;
int ind[M];
Point tmpPs[N]; // 存储与边界的交点
int cakeSize[N];vector<Point> insectCircle(const Point& A, const Point& dir, double r) {vector<Point> ans;Point P = A + dir * dot(dir, -A);double op = abs(cross(dir, A));if (cmp(op - r) > 0)return ans;double Pb = sqrt(r * r - op * op);if (cmp(Pb) == 0) ans.push_back(P);else {ans.push_back(P + dir * Pb);ans.push_back(P - dir * Pb);}return ans;
}// 按照极坐标排序
bool sortcmp(int i, int j) {Point pi = oiPs[i] - lowPoint;Point pj = oiPs[j] - lowPoint;// 在上下半区不同侧,上半区优先if (cmp(pi.y * pj.y) < 0) return pi.y > pj.y;pi /= pi.dis();pj /= pj.dis();// 有一条为1,0, x大的优化if (cmp(pi.x - 1) == 0 || 0 == cmp(pj.x - 1)) return pi.x > pj.x;double d = cmp(cross(pi, pj)); // 同侧判断是否逆时针旋转return d > 0;
}bool oneLine(int n) {if (n < 3)return true;for (int i = 2; i < n; ++i) {if (cmp(cross(oiPs[1] - oiPs[0], oiPs[i] - oiPs[0])) != 0) return false;}return true;
}void  solve() {int n, r;scanf("%d%d", &r, &n);for (int i = 0; i < n; ++i) {scanf("%lf%lf", &oiPs[i].x, &oiPs[i].y);scanf("%d", cakeSize+i);oiPs[i].id = i;}// 判断是否共线if (oneLine(n)) {// 选取最大的int indbig = 0;for (int i = 1; i < n; ++i) {if (cakeSize[i] > cakeSize[indbig])indbig = i;}Point dir = oiPs[n - 1] - oiPs[0];dir = { -dir.y, dir.x };dir /= dir.dis();auto insectPs = insectCircle(oiPs[indbig], dir, r);Point seat = insectPs[0];printf("%.10f %.10f\n", seat.x, seat.y);return;}oiPs[n] = oiPs[0];de.init(n, oiPs);auto oiedges = de.getEdge();vector<vector<int>> link(n, vector<int>());for (auto oie : oiedges) {link[oie.first].push_back(oie.second);}int maxSize = 0;Point seat;for (int i = 0; i < n; ++i) {// 遍历每个边界点,收集邻边,并按照逆时针排序。int len = 0;for (auto to : link[i]) {ind[len++] = to;}lowPoint = oiPs[i];sort(ind, ind + len, sortcmp);ind[len] = ind[0];// 添加循环优化// 求voronoi 边界之间交点bool isInsect = false; // 标记vonoroi cell 与圆是否有交集for (int i = 0; i < len && !isInsect; ++i) {Point mid = (lowPoint + oiPs[ind[i]]) / 2;Point dir = oiPs[ind[i]] - lowPoint;dir = { -dir.y, dir.x }; // 旋转90度Point mid2 = (lowPoint + oiPs[ind[i + 1]]) / 2;Point dir2 = oiPs[ind[i + 1]] - lowPoint;dir2 = { -dir2.y, dir2.x }; // 旋转90度// 判断是否为都边界(夹角不能大于180)if (cmp(cross(dir, dir2)) <= 0) {isInsect = true;break;}// 求交点 auto pr = intersection(mid, dir, mid2, dir2);Point ablePoint = mid2 + dir2 * pr.second;if (cmp(ablePoint.dis() - r) >= 0)isInsect = true;}int k = 0;// 求与圆的交点for (int i = 0; i < len && isInsect; ++i) {Point mid = (lowPoint + oiPs[ind[i]]) / 2;Point dir = oiPs[ind[i]] - lowPoint;dir = { -dir.y, dir.x }; // 旋转90度dir /= dir.dis();auto insectPs = insectCircle(mid, dir, r);for (auto& c : insectPs) {tmpPs[k++] = c;//c.out();}}// 排除无效交点for (int i = 0; i < len; ++i) {Point mid = (lowPoint + oiPs[ind[i]]) / 2;Point dir = oiPs[ind[i]] - lowPoint;dir = { -dir.y, dir.x }; // 旋转90度for (int j = 0; j < k; ++j) {// 判断点是否与Lowpoint 在同一半平面内if (cmp(cross(lowPoint - mid, dir) * cross(dir, tmpPs[j] - mid)) > 0) {swap(tmpPs[k - 1], tmpPs[j]);k--;j--;continue;}}}for (int j = 0; j < k; ++j) {if (cakeSize[i] > maxSize) {maxSize = cakeSize[i];seat = tmpPs[j];//seat.out();}}}printf("%.10f %.10f\n", seat.x, seat.y);//printf("%d\n", maxSize);//printf("%.10f\n", seat.dis());
}int main() {solve();return 0;}/*
10 5
0 0 100
1 0 1
0 1 2
0 -1 3
-1 0 410 3
1 -1 100
2 2 200
-2.5 -2.56 110 5
0.2 0 100
1 0 1
2 0 2
3 0 1
4 0 410 5
9.89 0 100
1 0 1
2 0 2
3 0 1
4 0 410 2
10 0 100
1 0 1*/

本人码农,希望通过自己的分享,让大家更容易学懂计算机知识。创作不易,帮忙点击公众号的链接。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/178058.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Amazon】AWS实战 | 快速发布安全传输的静态页面

文章目录 一、实验架构图二、实验涉及的AWS服务三、实验操作步骤1. 创建S3存储桶&#xff0c;存放网站网页2. 使用ACM建立域名证书3. 设置Cloudfront&#xff0c;连接S3存储桶✴️4. 设置Route53&#xff0c;解析域名服务5. 通过CLI工具上传网页更新内容【可选】 四、实验总结 …

构建强大的Web应用之Django详解

引言&#xff1a; Django是一个功能强大且灵活的Python Web框架&#xff0c;它提供了一套完整的工具和功能&#xff0c;帮助开发者快速构建高效的Web应用。本篇文章将带您逐步了解Django的基本概念和使用方法&#xff0c;并通过实际的代码案例&#xff0c;帮助您从零开始构建自…

YOLOv8添加AIFI(Attention-based Intrascale Feature Interaction模块替换SPPF模块)

1. 引言 1.1 相关介绍 模块名称&#xff1a;Attention-based Intrascale Feature Interaction 论文名称&#xff1a;RT-DETR: DETRs Beat Yolos on Real-time Object Detection 这是论文中的图&#xff0c;此处将其中的AIFI模块拿过来改进YOLOv8。 1.2 其他可改进SPPF模块 …

SpringCloud(一) 服务架构的演变及注册RestTemplate实现服务的远程调用

目录 一, 服务架构的演变 1.1 单体架构 1.2 分布式架构 1.3 微服务 1.4 SpringCloud 二, 服务拆分和远程调用 2,1 服务拆分原则 2.2 服务拆分示例 2.3 创建相应数据库 2.4 实现远程调用示例 1, 更改需求 2, 注册RestTemplate实现远程调用 2.5 服务消费者和提供者 一…

Pinia的十个简答小案例

1. 使用Pinia进行状态管理&#xff1a; import { defineStore } from piniaexport const useCounterStore defineStore({id: counter,state: () > ({count: 0}),actions: {increment() {this.count},decrement() {this.count--}} }) 2. 在组件中使用Pinia&#xff1a; &…

Nginx搭载负载均衡及前端项目部署

目录 ​编辑 一.Nginx安装 1.安装所需依赖 2.下载并解压Nginx安装包 3.安装nginx 4.启动Nginx服务 二.Tomcat负载均衡 1.准备环境 1.1 准备两个Tomcat 1.2 修改端口号 1.3 配置Nginx服务器集群 2.效果展示 ​编辑三.前端项目打包 ​编辑四.前端项目部署 1.上传项目…

Mysql系列 -索引模型数据结构

索引就是排好序的数据结构&#xff0c;可以帮助我们快速的查找到数据&#xff0c;那么底层的数据到底是如何存储的呢&#xff1f; 为什么InnoDB 用的是Btree 存储结构&#xff1f; 大家可以看看这个可视化的网站 数据结构和算法的可视化工具 可以看到数据结构里面有链表&…

Postman接口自动化测试之——批量参数化(参数文件)

Postman接口请求中的参数引用格式&#xff1a;{{参数名}}参数文件只适用于集合中。 创建参数文件 以记事本举例&#xff0c;也可以使用其他编辑器&#xff1b;第一行参数名&#xff0c;用半角逗号&#xff08;英文逗号&#xff09;隔开&#xff0c;第二行为参数值&#xff0c…

Linux -----------------------Shell 流程判断

什么是shell Shell是操作系统的用户界面&#xff0c;负责接收和解释用户的命令&#xff0c;并将这些命令转化为操作系统内部能够理解的指令&#xff0c;然后执行相应的操作。Shell还允许用户编写脚本&#xff0c;以自动化和批处理任务&#xff0c;从而提高效率。在Linux系统中&…

100G QSFP28 BIDI LR1光模块最新解决方案

上期文章我们有介绍到100G QSFP28 BIDI ER1 Lite光模块&#xff0c;本期内容我们将继续为大家介绍100G光模块系列的100G QSFP28 BIDI LR1光模块。这款产品同样也在易天ECOC光通讯展展出&#xff0c;下面跟着小易一起来看看吧&#xff01; 易天光通信的100G QSFP28 BIDI LR1单纤…

Redis中String类型的命令

目录 Redis中的内部编码 redis的数据结构和内部编码 Redis中的String类型 String类型的常见命令 set get mget mset String类型的计数命令 incr incrby decr incrbyfloat 其他命令 append getrange setrange strlen String类型的内部编码 Redis中的内部编码…

【源码】智能导诊系统,通过患者的主诉症状,自动匹配挂号科室和医生

随着计算机技术、网络技术、医院内网、智能终端的发展成熟&#xff0c;自动化、智能化就诊将是未来医院的发展模式。在目前综合性医疗机构&#xff0c;医院建设物庞大且复杂&#xff0c;接待就诊人员数量较大&#xff0c;医院诊疗科室众多&#xff0c;就诊人员容易迷失其中&…

Vue监听事件

一、问题场景 项目有个需求&#xff0c;在登录页面&#xff0c;输入好账号密码后&#xff0c;直接可以点击回车就能够登录&#xff0c;效果和点击登录按钮一样&#xff0c;登录页面源码如下 <template><body id"poster"><el-form class"login-…

linux 下 物理迁移 mysql 数据库 不能启动问题

1、授权问题 # chown -R 777 /app/db/mysql 2、/etc/my.cnf配置问题 [mysqld] basedir/app/db/mysql datadir/app/db/mysql/data socket/app/db/mysql/mysql.sock.lock innodb_buffer_pool_size128M innodb_force_recovery 1 symbolic-links0 [mysqld_safe] log-error/app/…

嵌入式linux常用的文件传输方式

做嵌入式就避免不了移植工作&#xff0c;所谓移植就是将交叉编译生成的可执行程序&#xff0c;库&#xff0c;配置文件等传输到开发板上进行工作。 常用传输方式有以下几种&#xff1a;1.串口传输 就是使用串口传输工具rz/sz; 该工具通过串口传输在SRT串口工具…

软文推广没效果?媒介盒子分享软文优化技巧

虽然软文推广能够为企业实现品牌增值&#xff0c;但也有许多企业在推广过程中犯错导致宣传没有效果&#xff0c;今天媒介盒子就来和大家聊聊企业在进行软文推广中的常见问题以及优化技巧。 问题1&#xff1a;内容生硬无法自然融入品牌信息 这也是企业在软文写作中较常出现的问…

win7中安装node14和vue

下载并安装低版本node 13 到官网去找早期历史版本的 nodejs 13 msi格式即可&#xff0c;并一键安装&#xff0c;我安装在了 D:\Program Files\nodejs 目录下 https://nodejs.org/download/release/v13.14.0/ 下载高版本node 14 下载高版本的node zip包 https://nodejs.org/…

生产环境docke问题排查

查看进程top查看具体的线程 top -H -p 8898如果cpu 过高&#xff0c;就是有问题的地方&#xff1b; 接下来根据docker查看具体的问题 查看dockers容器哪个内存、cpu占用过高 docker stats前言&#xff1a; 有java 启动容器&#xff1b;有jre包启动的容器。如下图 根据cpu很高…

【flink】Task 故障恢复详解以及各重启策略适用场景说明

文章目录 一. 重启策略种类&#xff08;Restart Strategies&#xff09;1. Fixed Delay Restart Strategy2. Failure Rate Restart Strategy3. Fallback Restart Strategy4. No Restart Strategy 二. 故障恢复策略&#xff08;Failover Strategies&#xff09;1. &#xff08;全…

【pdf密码】PDF没有密码,为什么不能编辑?

打开PDF文件的时候&#xff0c;没有提示带有密码&#xff0c;但是打开文件之后发现没有办法编辑PDF文件&#xff0c;这个是因为PDF文件设置了限制编辑&#xff0c;我们需要将限制取消才能够编辑文件。 那么&#xff0c;我们应该如何取消密码&#xff0c;编辑文件呢&#xff1f…