题目传送门:P2742 [USACO5.1] 圈奶牛Fencing the Cows /【模板】二维凸包 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
另:由于一些文章的疏忽,导致一些错别字,代码错误,公式错误导致大家的理解和误导,到后期作者会进行更改,感谢大家!!!(如果有发现勘误请大家联系作者)
前言:
本题涉及到计算几何、分治,凸包,这道题相对来说比较的难(对我来说一点都不难),可可能对那些计算几何、凸包的初学者理解起来较为抽象,刚好下一期的文章就是写关于计算机几何,凸包的知识分析与理解,帮助大家。
题目来自:
题目来自USACO是美国计算机奥林匹克竞赛英文名:Wntied States of America Computing
Olympiad的简称 这道题在洛谷里并没有却出明确的难度,作者自己评了一下,可以给到银级的难度。
1、题目分析:
本题的目标是计算能够围住给定的 n
个放牧点的最短围栏长度。从几何角度来看,这个最短的围栏实际上就是这些点的凸包的周长。凸包是包含所有给定点的最小凸多边形,它的特点是凸包上的点使得多边形的内部角度都小于或等于 180 度。
2、Graham扫描算法:
1、找到最下方的点:
首先,我们需要找到所有点中 y 坐标最小的点,如果有多个点 y 坐标相同时,则选择其中 x 坐标最小的点。这个点将作为凸包构建的起始点,因为它一定是凸包上的点。
在代码视线中,我们使用 函数和 函数来实现这一个目的。 函数会将点集按照 y 坐标排序,如果 y 坐标相同,则按照 x 坐标排序这样第一个点就是我们要找的最下方的点。
2、对其余点进行极角排序:
对于除最下方点外的其余点,我们将它们按照相对于最下方点的极角进行排序。极角排序可以使用叉积来实现。叉积计算公式:
对于三个点 O、 A 和 B,如果叉积 cp (o,a,b)大于0,则B在OA的逆时针方向;如果等于0,则O、A、B共线;如果小于0,则B在OA的顺时针的方向。
在 cl 函数中,先将所有点按照极角排序,排序依据是 cp 函数。
3、构建凸包:
我们使用一个栈(用vector<Point>模拟)来存储凸包上的点。
首先我们将最下方的点和排序后的第二个点加入栈中。
然后遍历上下的点,对于每个新点,检查它是否会使栈顶的点和栈顶前一个点构成的边向左转,即叉积大于0。
如果不是向左转(叉积小于等于 0),则将栈顶元素弹出,直到新点使栈顶元素和栈顶前一个元素构成的边向左转或栈中元素数量小于 2。然后将新点加入栈中。这一过程会构建出凸包的下部分。
为了构建上部分的凸包,我们从倒数第二个点开始,执行相同的操作,将点加入栈中,直到遍历完所有点。
最后需要将最后一个重复添加的点从栈中弹出,因为在构建上凸包时会重复添加起始点。
4、计算凸包周长:
对于存储在 h 中的凸包上的点,使用 distance 函数计算相邻点之间的距离。
distance 函数函数使用欧几里得距离公式:
来计算两点之间的距离。
将所有相邻点的距离相加,最后加上最后一个点和第一个点之间的距离,就得到了凸包的周长,也就是最短围栏的长度。
3、输入、出处理:
1、输入:
首先从标准的输入读取一个整数 n ,表示点的数量。
然后读取 n 行,每行包含一个点的 x 和 y 坐标。
2、输出:
计算出最短围栏长度(凸包周长)后,将结果保留并2位小数输出。
5、示例:
假设我们输入为:
4
4 8
4 12
5 9.3
7 8
- 首先找到最下方的点,经过排序后是
(4, 8)
。 - 对其他点进行极角排序。
- 构建凸包,通过
cl
函数,得到凸包上的点序列。 - 使用
perimeter
函数计算凸包周长,最终将结果输出,保留两位小数。
6、代码:
#include <bits/stdc++.h>
using namespace std;
struct Point {
double x;
double y;
};
double cp(const Point& O, const Point& A, const Point& B) {
return (A.x - O.x) * (B.y - O.y) - (A.y - O.y) * (B.x - O.x);
}
bool c(const Point& p1, const Point& p2) {
if (p1.y == p2.y) {
return p1.x < p2.x;
}
return p1.y < p2.y;
}vector<Point> cl(vector<Point>& points) {
int n = points.size();
if (n <= 1) {
return points;
}
sort(points.begin(), points.end(), c);
vector<Point> hull;
for (int i = 0; i < n; ++i) {
while (hull.size() >= 2 && cp(hull[hull.size() - 2], hull.back(), points[i]) <= 0) {
hull.pop_back();
}
hull.push_back(points[i]);
}
int lll = hull.size();
for (int i = n - 2; i >= 0; --i) {
while (hull.size() > lll && cp(hull[hull.size() - 2], hull.back(), points[i]) <= 0) {
hull.pop_back();
}
hull.push_back(points[i]);
}
hull.pop_back();
return hull;
}
double d(const Point& p1, const Point& p2) {
double dx = p1.x - p2.x;
double dy = p1.y - p2.y;
return sqrt(dx * dx + dy * dy);
}
double pi(const vector<Point>& hull) {
double p = 0;
int n = hull.size();
for (int i = 0; i < n - 1; ++i) {
p += d(hull[i], hull[i + 1]);
}
p += d(hull[n - 1], hull[0]);
return p;
}
int main() {
int n;
cin >> n;
vector<Point> points(n);
for (int i = 0; i < n; ++i) {
cin >> points[i].x >> points[i].y;
}
vector<Point> h = cl(points);
double l = pi(h);
cout << fixed << setprecision(2) << l << endl;
return 0;
}
总的来说其实这道题一点都不难的,虽然计算几何和凸包理解起来比较抽象,但是只要能理解,无论有多难,都是能啃下来的