当前位置: 首页 > news >正文

【超多图!笔记】[HNOI2008] 洛谷P3194 水平可见直线 [半平面交]

我真是没辙了要画图,还不知道会不会挂。。

温馨提示:本文不是题解,只是根据董晓老师的半平面交教程做的笔记,配合视频会更好理解。

视频链接(强烈推荐!!膜拜老师TNT)

前置知识:

向量定义(自行百度或问 AI 就行)

知道叉积是什么,是怎么算的,了解右手法则

(向量向量就是叉积,右手立起来对准向量,四根手指指向的终点,再把四根手指往里收,恰好指向的终点,满足这个规则的俩向量叉积就大于

注意:叉积不满足交换律!!

极角定义及其计算方法:

极角是一个向量与x轴正方向之间的夹角。

以下点 (x,y) 为从 (0,0) 出发指向 (x,y) 的向量。

举例:

  • 点 (1, 0) 的极角是 0°
  • 点 (0, 1) 的极角是 90°
  • 点 (-1, 0) 的极角是 180°
  • 点 (0, -1) 的极角是 270° 或 -90°

计算方法:

其实就是求的反函数值(自行百度),一般的取值范围是

(弧度制度)

但我们编程中有个直接就可以得出该向量的极角,取值范围是

(也就是包揽了整个度)

题面:

洛谷P3194

正无穷往下看,只能看见多边形的边,就是半平面交。

解析:

先上注释版代码:

#include <bits/stdc++.h> using namespace std; const int N = 5e4 + 10; const double eps = 1e-12; // 极小值,用于浮点数比较,根据题目定 1e-多少 int n, ans[N]; struct point { double x, y; //点 }; struct line { point s, e; //向量,包含起点终点和编号 int id; //原始编号会因排序混乱,应题目要求输出答案时需要 } a[N], q[N]; //以下 operator都是重载运算符 point operator+(point a, point b) { //向量相加 return {a.x + b.x, a.y + b.y}; } point operator-(point a, point b) { //向量相减 return {a.x - b.x, a.y - b.y}; } point operator*(point a, double t) { //向量数乘 return {a.x * t, a.y * t}; } double operator*(point a, point b) { //叉积 return a.x * b.y - a.y * b.x; } double angle(line a) { // atan2(y,x)计算向量 (x,y)的极角(范围 -π到 π) return atan2(a.e.y - a.s.y, a.e.x - a.s.x); } bool cmp(line a, line b) { //按极角给向量排序 //(极角从小到大排序了就好框多边形) double A = angle(a), B = angle(b); if (fabs(A - B) > eps) { //角度有很明显差别时 return A < B; } else { //角度差距不明显(平行)时 //注意这下面的 *都是重载过的叉积运算 return (a.e - a.s) * (b.e - a.s) < 0; //用叉积判断 b在不在 a的左边 //注意一个是向量 a,一个是 a的起点到 b的终点!! //就是右手法则,如果 a在 b的右边上面这一坨就大于 0,反之小于 0 //如果 a在 b的左边,则 a比 b优,那原先顺序不变,对应下面的: // if (angle(a[i]) - angle(a[i - 1]) < eps) continue; //搞过前面的 i-1 (a) 就不用搞 i (b) 了 } } point cross(line a, line b) { //求俩向量交点 point u = a.s - b.s; // b起点到 a起点 point v = a.e - a.s; // a起点到 a终点 point w = b.e - b.s; // b起点到 b终点 double t = (u * w) / (w * v); return a.s + v * t; //看不懂这段就先别看,看我代码下面的解释和图片 } bool right(line a, line b, line c) { point p = cross(b, c); // p是交点 return (a.e - a.s) * (p - a.s) <= 0; //这里我下面也有画图 //就是 a起点到 a终点的向量和 a起点到 bc交点的向量 判断位置,同上右手法则 //=0 代表俩向量重合,上面不判 =是因为题目说过直线不会相互重合 } void half_plane() { sort(a + 1, a + n + 1, cmp); //按极角排序 int head = 1, tail = 1; q[1] = a[1]; for (int i = 2; i <= n; i++) { if (angle(a[i]) - angle(a[i - 1]) < eps) { continue; //跳过角度相同的直线,保留最左侧 } //检查队尾直线是否无效 //当队尾 q[t]与队尾第二条直线 q[t-1]的交点 //在新直线 a[i]的右侧时,队尾直线无效 while (head < tail && right(a[i], q[tail], q[tail - 1])) { tail--; } q[++tail] = a[i]; } int len = 0; for (int i = head; i <= tail; i++) { //还在队里面的就是能框成多边形的,也就是能看见的 len++; ans[len] = q[i].id; } sort(ans + 1, ans + len + 1); for (int i = 1; i <= len; i++) { cout << ans[i] << " " ; } cout << endl; } int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> n; for (int i = 1; i <= n; i++) { double A, B; cin >> A >> B; a[i] = {{0, B}, {1, A + B}, i}; //取 x为 0和 1的点 } half_plane(); return 0; }

(正常的半平面交的板子是有踢队头的,不过本题很特殊。

一开始的那条边肯定能看到,因为斜率最小的直线在总是最高的。

因此它总是可见的,永远不会被完全覆盖,于是双端队列退化成单调栈了)

其实整个代码最需要理解的地方就是两条线段(直线)的交点,

和踢队尾的时候叉积是怎么发挥作用的

首先来看交点:

(以下图片建议配合代码 cross 函数食用)

图中深蓝和红色的是要计算交点的向量,浅蓝是向量到交点的延长线,粉色同理。

我们都知道,叉积的绝对值,就是俩向量构成的平行四边形面积的值,所以也就有:

(淡紫色的平行四边形是,浅黄色的平行四边形是

代码中设,也就是俩平行四边形面积比,

又因为俩四边形底相同,所以面积比就是与底相对应的高的比

(蓝色加粗的是的高,红色加粗的是的高)

我们又发现,因为平行四边形两底相等,

所以可以把那条红色的高平移到粉色下面,刚好接着向量的终点:

平移过来后,我们发现新构成的俩三角形是相似的,

而它们的高比就等于斜边比,也就是向量的长度与向量延长到交点的长度之比

所以代码最后就轻松求出了交点。

这时就有同学要问了:求交点直接代入俩直线方程不好吗?

哎,还真没错,但你想想,如果要处理直线交点,还有特殊情况:

  • 平行线(无交点)
  • 重合线(无限多交点)

写判断起来可麻烦,更别说你还得解方程。

而且俩直线斜率很接近,几乎平行时,那下面的分母就会很小,精度问题也要考虑。

而向量就没这烦恼,不用分情况讨论,几乎平行的时候也不是大除以小,是小除以小()。

再来看看怎么踢队尾的:

(橙色的是由的起点指向俩队尾交点的向量)

这时候根据右手法则,叉积大于,也就是三直线能好好相处,构成多边形。

right 函数返回,不用踢队尾。

另外一种情况:

这时候不满足右手法则,叉积小于,构成多边形显然不需要队尾,于是踢队尾。

当然还有叉积等于的情况,也就是向量和向量在同一条直线上

这样构成多边形和队尾也没啥关系,照样踢。

题单

【题单】半平面交-CSDN博客

http://www.jsqmd.com/news/1183109/

相关文章:

  • 推荐一下南京优质的吊车租赁公司:甄选 - 品牌推广大师
  • 产权解押委托书公证 2026 年办理流程及材料汇总 - 跑政通
  • ESG量化与跨境合规AI:绿色金融的新基建
  • Windows 11终极优化指南:Win11Debloat深度配置与性能调优实战
  • Ino 项目常见问题解决方案
  • DeepSeek-R1: Incentivizing Reasoning Capability in LLMs via Reinforcement Learning
  • WPS-Zotero插件终极指南:5分钟实现科研写作效率革命
  • 适合银行/证券行业的智能财务Agent有哪些?金融级智能自动化方案全景解析
  • 实战:功率分析仪3u3v测三相原理及接线
  • 2026 哈尔滨专业防水补漏实用指南一小时上门全场景渗漏根治方案 - 徽顺虹
  • QuickLookVideo:让Mac原生支持200+视频格式预览的开源神器
  • 开机直接黑屏、完全不弹出 GRUB 引导界面
  • 2026年07月:LED显示屏供应厂家实力解析——从技术路径到服务能力的全维度评估 - 甄选服务推荐
  • 教育机构如何通过结构化内容提升在AI搜索中的可见度
  • 东莞重切削加工中心推荐厂家! - 品牌推荐大师
  • 亲身到店探访合肥亨得利官方名表服务中心|服务热线及全部维修详细地址(2026年7月更新) - 亨得利官方
  • 2026年CPPM考试流程怎么选|众智商学院 - 众智商学院官方
  • 如何看待阿里内部全面禁用 Claude Code?
  • 2026年实力之选:欧松板、实木板、千年舟板材与石膏板、阻燃板、玻镁板、碳晶板、防撞板选择指南 - 甄选服务推荐
  • 如何为FMOD Studio GDExtension贡献代码:社区开发指南与规范
  • TSMaster【第三篇:神兵认主——TSMaster开发环境开锋 】
  • 基于paddlle架构的双摄像头下的行人重识别
  • 2026年7月古风表演宫宴品牌哪家好,宫宴/餐厅/餐饮/餐秀,宫宴门票预约找哪家 - 品牌推荐师
  • 如何用LTX-2.3-22b-IC-LoRA-LipDub实现专业级视频配音:新手完整指南
  • 亲身探访天津卡地亚官方售后服务中心|全新地址及服务热线(2026年7月最新) - 卡地亚服务中心
  • 2026 年社旗诚信的水下管道铺设工厂推荐,敢把几万吨钢材沉入海底?这操作颠覆你的工程认知 - 企业推荐官【认证】
  • 运营商格局重塑:移动领跑,广电崛起
  • 重庆办公家具出售公司哪家正规 - 品牌推广大师
  • 华为OD机试真题 新系统 2026-07-05 C++ 实现【SQL记录拆分】
  • GitZip Pro 源码解析:一个 GitHub 文件/文件夹下载扩展是如何工作的(二)GitHub 页面注入与文件选择