WHUT第十周训练整理
写在前面的话:我的能力也有限,错误是在所难免的!因此如发现错误还请指出一同学习!
索引
(难度由题目自身难度与本周做题情况进行分类,仅供新生参考!)
零、基础知识过关
一、easy:07、08、10、11、12、16、17
二、medium:01、09、15、19、21、22、23、24、25
三、hard:14、18、20、26
四、超纲:02、03、04、05、06、13
本题解报告大部分使用的是C++语言,在必要的地方使用C语言解释。
零、基础知识过关
图论手开始学习计算几何咯…这周与其说是计算几何场,不如说是凸包场,然后还有好多超纲的题,不是我们现阶段应该考虑的东西,所以这个报告就放出这些题的题解了。
下面一起学习一下计算几何的基础吧。
1. 精度问题
在计算几何和其他需要用浮点数的问题中,一定要考虑精度的问题。
需要引入一个极小量 ,具体的数值可以根据题目而定,一般我设成 1e-9 差不多足够了。
判断两个浮点数相同:,而不是 ;
判断大于关系:,而不是 ;
判断小于关系:,而不是 ;
输出精度也需要注意,人为的控制输出的长度,后面的数据将会被四舍五入。
// c
scanf("%.4f", ans);
// c++
cout << fixed << setprecision(4) << ans << endl;
2. 向量
有大小有方向的量,又称为矢量。
二维的向量常用一个对数 表示,代码中常用一个结构体来实现向量。可以发现这样的存储跟二维平面中点的存储方式是一致的,所以向量也可以用点的结构体保存。
struct vector// point
{
double x,y;
vector (double X=0,double Y=0)
{
x=X,y=Y;
}
}
向量的模:即向量的长度。设向量 ,则 。
需要注意 (1)点 向量 点 (2)点 点 向量
3. 极角
对于向量 ,可以用函数 来计算他的极角
按照极角为关键字排序后的顺序为极角序。
4. 点积
的几何意义为 在 上的投影长度乘以 的模长
,其中 为 之间的夹角。
座标表示
点积的应用
(1)判断两个向量是否垂直 <=>
(2)求两个向量的夹角,点积 为钝角,点积 为锐角
(3)求一个向量的模长,
5. 法向量
与单位向量垂直的向量称为单位法向量
6. 二维叉积
两个向量的叉积是一个标量, 的几何意义为他们所形成的平行四边形的有向面积。
座标表示
直观理解,假如 在 的左边,则有向面积为正,假如在右边则为负。假如 共线,则叉积为 。
2~6 参考自
计算几何总结 clover_hxy
https://blog.csdn.net/clover_hxy/article/details/53966405>
7. 全整数
在某些精度要求较高的题目中,可能可以使用 long double 类型给糊弄过去,实际上让整个程序中出现的变量全为整数,对精度控制是最好的。例如全转为向量表示,利用叉积等知识把原先需要用浮点类型的东西利用元组来唯一的表示出来。
8. 计算几何相关算法
一维
- 点的表示
二维
- 线段(直线)的表示
- 多边形的表示
- 圆的表示
- 点线之间的关系(距离,是否在线上)
- 点和多边形的关系(是否在内部,多边形内部(边)点数量,各种性质的点)
- 点和圆的关系(是否在内部,最小圆覆盖,圆内(边)点数量)
- 线线之间的关系(位置关系,求交点,向量,夹角)
- 线和多边形的关系(位置关系,交点,平分或多分)
- 线和圆的关系(位置关系,交点,各种圆的性质)
- 多边形之间的关系(位置关系,面积并,半平面交,旋转卡壳,最大空凸包)
- 圆圆之间的关系(位置关系,切线关系,各类多边形外切圆内切圆,面积并,反演)
三维
二维问题都可以在三维中对应,具体性质都会发生变化。
推荐模板
一、easy
1007:You can Solve a Geometry Problem too(线段相交)
题意:给出 条线段的端点座标 ,现在问一共有多少个交点,重复的交点也需要多次计算。
范围:, 和 是浮点数
分析: 判断线段相交板子题。双重循环调用函数判断线段 与线段 是否相交,统计答案。
Code:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100 + 10;
// --------------以下均为板子----------------------
struct Point
{ //点
double x, y;
Point() {}
Point(int a, int b)
{ //方便赋值
x = a;
y = b;
}
void input()
{ //定义输入函数方便用的时候
scanf("%lf%lf", &x, &y);
}
};
struct Line
{ //线段
Point a, b;
Line() {}
Line(Point x, Point y)
{
a = x;
b = y;
}
void input()
{
a.input();
b.input();
}
} line[MAXN];
bool judge(Point &a, Point &b, Point &c, Point &d)
{
if (!(min(a.x, b.x) <= max(c.x, d.x) && min(c.y, d.y) <= max(a.y, b.y) && min(c.x, d.x) <= max(a.x, b.x) && min(a.y, b.y) <= max(c.y, d.y))) //这里的确如此,这一步是判定两矩形是否相交
return false;
double u, v, w, z; //分别记录两个向量
u = (c.x - a.x) * (b.y - a.y) - (b.x - a.x) * (c.y - a.y);
v = (d.x - a.x) * (b.y - a.y) - (b.x - a.x) * (d.y - a.y);
w = (a.x - c.x) * (d.y - c.y) - (d.x - c.x) * (a.y - c.y);
z = (b.x - c.x) * (d.y - c.y) - (d.x - c.x) * (b.y - c.y);
return (u * v <= 0.00000001 && w * z <= 0.00000001);
}
// --------------以上均为板子----------------------
int main()
{
int n;
while (cin >> n, n)
{
for (int i = 0; i < n; i++)
{
line[i].input();
}
int ans = 0;
// 双重循环统计交点数量
for (int i = 0; i < n; i++)
{
for (int j = i + 1; j < n; j++)
{
if (judge(line[i].a, line[i].b, line[j].a, line[j].b))
ans++;
}
}
cout << ans << endl;
}
return 0;
}
1008:Pick-up sticks(暴力+判断线段相交)
题意:按顺序丢下 根木棍,两端点的座标为 ,现在问有哪几根木棍是没有被压住的。
范围:, 和 是整数
分析:这个数据范围真是吓死人了,真要严格按照这个范围的话感觉这题没法做啊。这道题只要暴力枚举每条边后面的所有边是否跟这条边相交,有的话说明被覆盖,没有的话就输出。注意还要输出最后的一条边,因为必定是不会被覆盖的。
Code:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
// --------------以下均为板子----------------------
struct Point
{ //点
double x, y;
Point() {}
Point(int a, int b)
{ //方便赋值
x = a;
y = b;
}
void input()
{ //定义输入函数方便用的时候
scanf("%lf%lf", &x, &y);
}
};
struct Line
{ //线段
Point a, b;
Line() {}
Line(Point x, Point y)
{
a = x;
b = y;
}
void input()
{
a.input();
b.input();
}
} line[MAXN];
bool judge(Point &a, Point &b, Point &c, Point &d)
{
if (!(min(a.x, b.x) <= max(c.x, d.x) && min(c.y, d.y) <= max(a.y, b.y) && min(c.x, d.x) <= max(a.x, b.x) && min(a.y, b.y) <= max(c.y, d.y))) //这里的确如此,这一步是判定两矩形是否相交
return false;
double u, v, w, z; //分别记录两个向量
u = (c.x - a.x) * (b.y - a.y) - (b.x - a.x) * (c.y - a.y);
v = (d.x - a.x) * (b.y - a.y) - (b.x - a.x) * (d.y - a.y);
w = (a.x - c.x) * (d.y - c.y) - (d.x - c.x) * (a.y - c.y);
z = (b.x - c.x) * (d.y - c.y) - (d.x - c.x) * (b.y - c.y);
return (u * v <= 0.00000001 && w * z <= 0.00000001);
}
// --------------以上均为板子----------------------
int main()
{
int n;
while (cin >> n, n)
{
for (int i = 0; i < n; i++)
{
line[i].input(); // 输入线段
}
int first = 1; // first控制格式
cout << "Top sticks: ";
// 暴力双重循环枚举检查木棍i是否被压住
for (int i = 0; i < n; i++)
{
for (int j = i + 1; j < n; j++)
{
if (judge(line[i].a, line[i].b, line[j].a, line[j].b))
{
break;
}
// 到这里说明没有木根压着木棍i
if (j == n - 1)
{
if (first)
first = 0;
else
cout << ", ";
cout << i + 1;
}
}
}
cout << ", " << n << "." << endl; // 别忘了输出最后一根
}
return 0;
}
1010:Cupid’s Arrow(判断点在多边形内部)
题意:给出一个 边形,需要判断 个点 是否在这个 边形内部(边缘不算)。
范围:, 的范围未给出
分析:判断点在多边形内部板子题,点已经按照顺时针排好序,直接调用函数判断即可。唯一需要注意的就是在边缘的点不算,要手动判断一下。
Code:
#include <bits/stdc++.h>
#define EPS 1e-8
using namespace std;
typedef long long LL;
const int N = 105;
struct point
{
double x, y;
};
point poly[N];
int n, m;
double dabs(double a)
{
return a < 0 ? -a : a;
}
double Min(double a, double b)
{
return a < b ? a : b;
}
double Max(double a, double b)
{
return a > b ? a : b;
}
bool on_line(point a, point b, point c) //判断点在直线上
{
if (c.x >= Min(a.x, b.x) && c.x <= Max(a.x, b.x) && c.y >= Min(a.y, b.y) && c.y <= Max(a.y, b.y))
{
return (dabs((c.x - a.x) * (b.y - a.y) - (b.x - a.x) * (c.y - a.y)) <= EPS);
}
return false;
}
bool inside_polygon(point p, int n) //判断点在多边形内
{
int counter = 0;
double xinter;
point p1, p2;
p1 = poly[0];
for (int i = 1; i <= n; i++)
{
p2 = poly[i % n];
if (on_line(p1, p2, p)) //此题在边界不算
return false;
if (p.y > Min(p1.y, p2.y)) //如果射线交于边的下端点,不算相交,所以是>
{
if (p.y <= Max(p1.y, p2.y))
{
if (p.x <= Max(p1.x, p2.x))
{
if (p1.y != p2.y) //如果射线和边重合,不算
{
xinter = (p.y - p2.y) * (p1.x - p2.x) / (p1.y - p2.y) + p2.x;
if (p1.x == p2.x || p.x <= xinter)
counter++;
}
}
}
}
p1 = p2;
}
if (counter % 2 == 0)
return false;
return true;
}
int main()
{
while (cin >> n)
{
for (int i = 0; i < n; i++)
{
cin >> poly[i].x >> poly[i].y;
}
cin >> m;
point p;
for (int i = 0; i < m; i++)
{
cin >> p.x >> p.y;
if (inside_polygon(p, n))
cout << "Yes" << endl;
else
cout << "No" << endl;
}
}
return 0;
}
1011:Shape of HDU(判断凸多边形)
题意:给一个有 个顶点 的多边形,问是否是凸多边形。
范围:无明确指出,均为整数
分析:凸包模板题。首先我们知道凸包一定是个凸多边形,那么我们只需要知道对这个多边形求凸包后的点数是否跟原多边形相等即可。
当然方法不止一种,可以参考:其他判断多边形的方法
Code:
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-9;
const double PI = acos(-1.0);
const int MAXN = 1e5 + 10;
int n;
// --------------以下均为板子----------------------
struct point
{
double x, y;
} points[MAXN], ans[MAXN];
bool mult(point sp, point ep, point op)
{
return (sp.x - op.x) * (ep.y - op.y) >= (ep.x - op.x) * (sp.y - op.y);
}
inline bool operator<(const point &l, const point &r)
{
return l.y < r.y || (l.y == r.y && l.x < r.x);
}
int graham(point pnt[], int n, point res[])
{
int i, len, top = 1;
sort(pnt, pnt + n);
if (n == 0)
{
return 0;
}
res[0] = pnt[0];
if (n == 1)
{
return 1;
}
res[1] = pnt[1];
if (n == 2)
{
return 2;
}
res[2] = pnt[2];
for (i = 2; i < n; i++)
{
while (top && mult(pnt[i], res[top], res[top - 1]))
{
top--;
}
res[++top] = pnt[i];
}
len = top;
res[++top] = pnt[n - 2];
for (i = n - 3; i >= 0; i--)
{
while (top != len && mult(pnt[i], res[top], res[top - 1]))
{
top--;
}
res[++top] = pnt[i];
}
return top; // 返回凸包中点的个数
}
// --------------以上均为板子----------------------
int main()
{
while (cin >> n, n)
{
for (int i = 0; i < n; i++)
{
cin >> points[i].x >> points[i].y;
}
int num = graham(points, n, ans);
if (num == n)
cout << "convex" << endl;
else
cout << "concave" << endl;
}
return 0;
}
1012:Cupid’s Arrow(判断点在多边形内部)
又重题了喂!1010。
1016:改革春风吹满地(多边形面积)
题意:逆时针给 个点 ,求这个多边形的面积。
范围:, 和 是整数
分析:求多边形面积板子题。
Code:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100 + 10;
// --------------以下均为板子----------------------
struct Lpoint
{
double x, y;
} points[MAXN]; // 点
double area_of_polygon(int vcount, Lpoint plg[])
{
int i;
double s;
if (vcount < 3)
{
return 0;
}
s = plg[0].y * (plg[vcount - 1].x - plg[1].x);
for (i = 1; i < vcount; i++)
{
s += plg[i].y * (plg[(i - 1)].x - plg[(i + 1) % vcount].x);
}
return s / 2;
}
// --------------以上均为板子----------------------
int main()
{
int n;
while (cin >> n, n)
{
for (int i = 0; i < n; i++)
{
cin >> points[i].x >> points[i].y;
}
cout << fixed << setprecision(1) << area_of_polygon(n, points) << endl;
}
return 0;
}
1017:Lifting the Stone(求多边形重心)
题意:逆时针给 个点 ,求这个多边形的重心。
范围:
分析:多边形重心板子题。
Code:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 10;
// --------------以下均为板子----------------------
struct point
{
double x, y;
} points[MAXN];
point bcenter(point pnt[], int n)
{
point p, s;
double tp, area = 0, tpx = 0, tpy = 0;
p.x = pnt[0].x;
p.y = pnt[0].y;
for (int i = 1; i <= n; ++i)
{ // point:0~n-1
s.x = pnt[(i == n) ? 0 : i].x;
s.y = pnt[(i == n) ? 0 : i].y;
tp = (p.x * s.y - s.x * p.y);
area += tp / 2;
tpx += (p.x + s.x) * tp;
tpy += (p.y + s.y) * tp;
p.x = s.x;
p.y = s.y;
}
s.x = tpx / (6 * area);
s.y = tpy / (6 * area);
return s;
}
// --------------以上均为板子----------------------
int main()
{
int T;
cin >> T;
while (T--)
{
int n;
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> points[i].x >> points[i].y;
}
point ans = bcenter(points, n);
cout << fixed << setprecision(2) << ans.x << " " << ans.y << endl;
}
return 0;
}
二、medium
1001:Everything Has Changed(余弦定理,判断两个圆的关系)
题意:在原点有个半径为 的大圆,还有 个圆心位于 且半径为 的小圆,大圆与小圆重叠的部分会被切割掉,现在问大圆的周长是多少?
范围:
分析:这道题看起来很复杂的样子,但是实际上我们只需要求相交的弧长。分类讨论大圆与各种小圆的关系:
① 小圆在大圆内部和外部(包括外切),不需要考虑,不会对大圆的周长造成影响。
② 小圆与大圆内切,此时只需要加上小圆的周长就可以了
③ 小圆与大圆相交
已知大圆的原周长为 ,而一个与之相交的小圆会让周长变成 。且题目保证小圆不会出现相交,那么每个小圆跟大圆的操作独立开来,对会相交的小圆计算弧长更新答案即可。
那么问题就转换成求弧长,自然想到要求圆心角。
三边都知道了,那么这里只需要用余弦定理就可以求出圆心角。
详见代码。
Code:
#include <bits/stdc++.h>
using namespace std;
const double eps = 1e-9;
const double PI = acos(-1.0);
int m, R;
int main()
{
int T;
cin >> T;
while (T--)
{
cin >> m >> R;
double ans = 2 * PI * R; // 原周长
for (int i = 0; i < m; i++)
{
double x, y, r;
cin >> x >> y >> r;
double len = sqrt(x * x + y * y); // 圆心距
// 情况1
if (len >= R + r || len < R - r)
continue;
// 情况2
if (len == R - r)
{
ans += 2 * PI * r;
continue;
}
// 情况3,利用余弦定理求出圆心角的一半再翻倍
double angle1 = (R * R + len * len - r * r) / (2 * R * len);
double angle2 = (r * r + len * len - R * R) / (2 * r * len);
angle1 = 2 * acos(angle1);
angle2 = 2 * acos(angle2);
ans = ans - angle1 * R + angle2 * r;
}
cout << fixed << setprecision(7) << ans << endl;
}
return 0;
}
1009:Segment set(并查集+线段相交)
不说了,上一周并查集/最小生成树场才刚刚做过,有兴趣的去看看上周的报告 1023。
https://blog.csdn.net/qq_41765114/article/details/104246134
Code:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000 + 10;
int n;
int fa[MAXN], num[MAXN];
struct Point
{ //点
double x, y;
Point() {}
Point(int a, int b)
{ //方便赋值
x = a;
y = b;
}
void input()
{ //定义输入函数方便用的时候
scanf("%lf%lf", &x, &y);
}
};
struct Line
{ //线段
Point a, b;
Line() {}
Line(Point x, Point y)
{
a = x;
b = y;
}
void input()
{
a.input();
b.input();
}
} line[MAXN];
// 判断线段相交
bool judge(Point &a, Point &b, Point &c, Point &d)
{
if (!(min(a.x, b.x) <= max(c.x, d.x) && min(c.y, d.y) <= max(a.y, b.y) && min(c.x, d.x) <= max(a.x, b.x) && min(a.y, b.y) <= max(c.y, d.y))) //这里的确如此,这一步是判定两矩形是否相交
return false;
double u, v, w, z; //分别记录两个向量
u = (c.x - a.x) * (b.y - a.y) - (b.x - a.x) * (c.y - a.y);
v = (d.x - a.x) * (b.y - a.y) - (b.x - a.x) * (d.y - a.y);
w = (a.x - c.x) * (d.y - c.y) - (d.x - c.x) * (a.y - c.y);
z = (b.x - c.x) * (d.y - c.y) - (d.x - c.x) * (b.y - c.y);
return (u * v <= 0.00000001 && w * z <= 0.00000001);
}
int find(int x)
{
return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void unin(int x, int y)
{
int fx = find(x), fy = find(y);
if (fx == fy)
return;
fa[fx] = fy;
num[fy] += num[fx];
}
int main()
{
int T;
cin >> T;
while (T--)
{
cin >> n;
// 初始化
for (int i = 1; i <= n; i++)
{
fa[i] = i;
num[i] = 1;
}
int index = 1;
for (int i = 0; i < n; i++)
{
char op;
cin >> op;
// 插入线段
if (op == 'P')
{
line[index++].input(); // 输入一条边
// 判断是否与之间的线段相交
for (int i = 1; i < index - 1; i++)
{
if (judge(line[i].a, line[i].b, line[index - 1].a, line[index - 1].b))
{
unin(i, index - 1);
}
}
}
// 查询
else
{
int x;
cin >> x;
int fx = find(x);
cout << num[fx] << endl;
}
}
if (T)
cout << endl;
}
return 0;
}
1015:Fermat Point in Quadrangle(四边形费马点)
题意:给四边形的四个点 ,求这个四边形的费马点。
范围:
分析:四边形分为凸四边形与凹四边形。
对于凸四边形,显然对角线上的点到两个端点的距离之和最小,那么两条对角线的交点就满足到四个端点的距离之和最小,因此凸四边形的费马点就是对角线的交点。
对于凹四边形,费马点一定就是那个凹进起来的点,如果选择其他的点,那么就会形成两个三角形,不会是更优解。
例如,选择凹进来的点的话答案是 ,选择其他点的答案是 ,显然 且 ,后者被前者完爆,其他情况类似,可以自己手动推一下。
Code:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 8 + 10;
const int INF = 0x3f3f3f3f;
// --------------以下均为板子----------------------
struct Point
{
double x, y;
} points[MAXN];
double xmult(Point p1, Point p2, Point p0)
{
return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
}
// 求两条线段的交点
Point intersection(Point p1, Point p2, Point p3, Point p4)
{
Point ret = p1;
double t = ((p1.x - p3.x) * (p3.y - p4.y) - (p1.y - p3.y) * (p3.x - p4.x)) / ((p1.x - p2.x) * (p3.y - p4.y) - (p1.y - p2.y) * (p3.x - p4.x));
ret.x += (p2.x - p1.x) * t;
ret.y += (p2.y - p1.y) * t;
return ret;
}
// --------------以上均为板子----------------------
double distance(Point a, Point b)
{
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
// 计算该点到四个顶点的距离之和
double len(Point x)
{
double sum = 0;
for (int i = 0; i < 4; i++)
{
sum += distance(x, points[i]);
}
return sum;
}
int main()
{
while (cin >> points[0].x >> points[0].y)
{
for (int i = 1; i < 4; i++)
{
cin >> points[i].x >> points[i].y;
}
int num = 0;
for (int i = 0; i < 4; i++)
{
if (points[i].x == -1 && points[i].y == -1)
num++;
}
if (num == 4)
break;
double ans = INF;
// 先计算选择四个顶点的最优解
for (int i = 0; i < 4; i++)
{
ans = min(ans, len(points[i]));
}
// 再计算对角线交点
// 叉积的乘积结果相同说明顺序正确
// 点的顺序为0123
if (xmult(points[0], points[1], points[3]) * xmult(points[1], points[2], points[3]) > 0)
{
ans = min(ans, len(intersection(points[0], points[2], points[1], points[3])));
}
// 点的顺序为0132
else if (xmult(points[0], points[1], points[2]) * xmult(points[1], points[3], points[2]) > 0)
{
ans = min(ans, len(intersection(points[0], points[3], points[1], points[2])));
}
// 点的顺序为0213
else
{
ans = min(ans, len(intersection(points[0], points[1], points[2], points[3])));
}
cout << fixed << setprecision(4) << ans << endl;
}
return 0;
}
1019:Quadrilateral(最大面积四边形)
题意:给四边形的四条边 ,问这四条边形成的最大面积四边形的面积是多少,如果不能形成四边形则输出 。
范围:
分析:首先我们要知道构成四边形的条件:最长边 > 剩余三边之和
其次我们可以发现最大面积的四边形一定是凸四边形,如果是凹四边形的话一定会造成面积的浪费,因此我们可以利用海伦公式求出凸四边形的面积。
Code:
#include <bits/stdc++.h>
using namespace std;
double arr[4];
int main()
{
int T;
cin >> T;
int kase = 1;
while (T--)
{
double a, b, c, d;
cin >> arr[0] >> arr[1] >> arr[2] >> arr[3];
sort(arr, arr + 4);
cout << "Case " << kase++ << ": ";
// 构成四边形的条件
if (arr[3] >= arr[0] + arr[1] + arr[2])
{
cout << "-1" << endl;
}
else
{
// 海伦公式
double p = (arr[0] + arr[1] + arr[2] + arr[3]) / 2;
double s = sqrt((p - arr[0]) * (p - arr[1]) * (p - arr[2]) * (p - arr[3]));
cout << fixed << setprecision(6) << s << endl;
}
}
return 0;
}
1021:Summer holiday(凸包)
题意:给 个点 ,问选择其中三个点形成的三角形的最大面积。
范围:, 和 是整数
分析:跟 1020 差不多的题目,稍微简单一点,也是求出凸包之后三重循环枚举凸包上面的点构成三角形统计答案即可。(听说在随机数据的情况下凸包上面的点的个数只有 个)
Code:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 10;
// --------------以下均为板子----------------------
struct point
{
double x, y;
} p[MAXN], ans[MAXN], base;
bool mult(point sp, point ep, point op)
{
return (sp.x - op.x) * (ep.y - op.y) >= (ep.x - op.x) * (sp.y - op.y);
}
double dis(point a, point b)
{
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
inline bool operator<(const point &l, const point &r)
{
return l.y < r.y || (l.y == r.y && l.x < r.x);
}
int cross(point sp, point ep, point op)
{
return (sp.x - op.x) * (ep.y - op.y) - (ep.x - op.x) * (sp.y - op.y);
}
bool cmp(point a, point b)
{
int ans = cross(a, b, base);
if (ans == 0)
return dis(base, a) > dis(base, b);
else
return ans > 0;
}
int graham(point pnt[], int n, point res[])
{
int i, len, top = 1;
sort(pnt, pnt + n);
if (n == 0)
{
return 0;
}
res[0] = pnt[0];
if (n == 1)
{
return 1;
}
res[1] = pnt[1];
if (n == 2)
{
return 2;
}
res[2] = pnt[2];
for (i = 2; i < n; i++)
{
while (top && mult(pnt[i], res[top], res[top - 1]))
{
top--;
}
res[++top] = pnt[i];
}
len = top;
res[++top] = pnt[n - 2];
for (i = n - 3; i >= 0; i--)
{
while (top != len && mult(pnt[i], res[top], res[top - 1]))
{
top--;
}
res[++top] = pnt[i];
}
return top; // 返回凸包中点的个数
}
// --------------以上均为板子----------------------
int main()
{
int n;
while (cin >> n)
{
for (int i = 0; i < n; i++)
{
cin >> p[i].x >> p[i].y;
}
base = p[0];
int pos = 0;
for (int i = 1; i < n; i++)
{
if (p[i].y < base.y || (p[i].y == base.y && p[i].x < base.x))
{
base = p[i];
pos = i;
}
swap(p[0], p[pos]);
}
sort(p + 1, p + n, cmp);
int num = graham(p, n, ans);
double res = 0;
for (int i = 0; i < num; i++)
{
for (int j = i + 1; j < num; j++)
{
for (int k = j + 1; k < num; k++)
{
res = max(res, 1.0 * cross(ans[i], ans[j], ans[k]));
}
}
}
cout << fixed << setprecision(2) << res / 2.0 << endl;
}
return 0;
}
1022:Surround the Trees(凸包周长)
题意:给 个点 ,现在用一根绳子将所有的点捆绑在内,问绳子的最短长度是多少。
范围:
分析:显然只需要考虑最外面点,所以求出凸包之后计算凸包的周长即可。唯一需要注意的就是多点共线的情况!此时只需要一条线!
Code:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 10;
// --------------以下均为板子----------------------
struct point
{
double x, y;
} p[MAXN], ans[MAXN], base;
bool mult(point sp, point ep, point op)
{
return (sp.x - op.x) * (ep.y - op.y) >= (ep.x - op.x) * (sp.y - op.y);
}
double dis(point a, point b)
{
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
inline bool operator<(const point &l, const point &r)
{
return l.y < r.y || (l.y == r.y && l.x < r.x);
}
int graham(point pnt[], int n, point res[])
{
int i, len, top = 1;
sort(pnt, pnt + n);
if (n == 0)
{
return 0;
}
res[0] = pnt[0];
if (n == 1)
{
return 1;
}
res[1] = pnt[1];
if (n == 2)
{
return 2;
}
res[2] = pnt[2];
for (i = 2; i < n; i++)
{
while (top && mult(pnt[i], res[top], res[top - 1]))
{
top--;
}
res[++top] = pnt[i];
}
len = top;
res[++top] = pnt[n - 2];
for (i = n - 3; i >= 0; i--)
{
while (top != len && mult(pnt[i], res[top], res[top - 1]))
{
top--;
}
res[++top] = pnt[i];
}
return top; // 返回凸包中点的个数
}
// --------------以上均为板子----------------------
int main()
{
int n;
while (cin >> n, n)
{
for (int i = 0; i < n; i++)
{
cin >> p[i].x >> p[i].y;
}
int num = graham(p, n, ans);
// 多个点都在一条线上,所以外侧只有两个点
if (num == 2)
{
cout << fixed << setprecision(2) << dis(ans[0], ans[1]) << endl;
continue;
}
// 计算周长
double res = 0;
for (int i = 0; i < num; i++)
{
res += dis(ans[i], ans[(i + 1) % num]);
}
cout << fixed << setprecision(2) << res << endl;
}
return 0;
}
1023:Wall(凸包)
题意:顺时针给 个点 ,现在要用最短长度的绳子将这个多边形包围起来,要满足所有位置绳子距离多边形至少为 。
范围:
分析:显然我们只需要考虑凸包上的点,因为我们需要花费最少,如果考虑内部的点,那么花费必然会增加。因此我们只需要把最外侧的点全部连起来,再这个新多边形的外侧 的距离包一圈即可。
直线的部分合起来就是凸包的周长,而曲线的部分拼接在一起会发现正好形成了一个半径为 的圆,因此我们只要计算凸包周长以及一个圆的周长即可。
Code:
#include <bits/stdc++.h>
using namespace std;
const double PI = acos(-1.0);
const int MAXN = 1000 + 10;
// --------------以下均为板子----------------------
struct point
{
double x, y;
} p[MAXN], ans[MAXN], base;
bool mult(point sp, point ep, point op)
{
return (sp.x - op.x) * (ep.y - op.y) >= (ep.x - op.x) * (sp.y - op.y);
}
double dis(point a, point b)
{
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
inline bool operator<(const point &l, const point &r)
{
return l.y < r.y || (l.y == r.y && l.x < r.x);
}
int cross(point sp, point ep, point op)
{
return (sp.x - op.x) * (ep.y - op.y) - (ep.x - op.x) * (sp.y - op.y);
}
bool cmp(point a, point b)
{
int ans = cross(a, b, base);
if (ans == 0)
return dis(base, a) > dis(base, b);
else
return ans > 0;
}
int graham(point pnt[], int n, point res[])
{
int i, len, top = 1;
sort(pnt, pnt + n);
if (n == 0)
{
return 0;
}
res[0] = pnt[0];
if (n == 1)
{
return 1;
}
res[1] = pnt[1];
if (n == 2)
{
return 2;
}
res[2] = pnt[2];
for (i = 2; i < n; i++)
{
while (top && mult(pnt[i], res[top], res[top - 1]))
{
top--;
}
res[++top] = pnt[i];
}
len = top;
res[++top] = pnt[n - 2];
for (i = n - 3; i >= 0; i--)
{
while (top != len && mult(pnt[i], res[top], res[top - 1]))
{
top--;
}
res[++top] = pnt[i];
}
return top; // 返回凸包中点的个数
}
// --------------以上均为板子----------------------
int main()
{
int T;
cin >> T;
while (T--)
{
int n, l;
cin >> n >> l;
// 这里进行了逆时针排序,其实是不必要的
for (int i = 0; i < n; i++)
{
cin >> p[i].x >> p[i].y;
}
base = p[0];
int pos = 0;
for (int i = 1; i < n; i++)
{
if (p[i].y < base.y || (p[i].y == base.y && p[i].x < base.x))
{
base = p[i];
pos = i;
}
swap(p[0], p[pos]);
}
sort(p + 1, p + n, cmp);
int num = graham(p, n, ans);
// 计算凸包的周长以及该圆的周长
double res = 2 * PI * l;
for (int i = 0; i < num; i++)
{
res += dis(ans[i], ans[(i + 1) % num]);
}
cout << (int)(res + 0.5) << endl;
if (T)
cout << endl;
}
return 0;
}
1024:最大三角形(凸包)
题意:给 个点 ,在这些点中寻找三个点构成面积最大的三角形,输出面积。
范围:
分析:按照题目说的来做就可以了,很简单。求出凸包之后三重循环枚举三个点利用叉积更新面积即可。
Code:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5e4 + 10;
// --------------以下均为板子----------------------
struct point
{
double x, y;
} p[MAXN], ans[MAXN], base;
bool mult(point sp, point ep, point op)
{
return (sp.x - op.x) * (ep.y - op.y) >= (ep.x - op.x) * (sp.y - op.y);
}
double dis(point a, point b)
{
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
inline bool operator<(const point &l, const point &r)
{
return l.y < r.y || (l.y == r.y && l.x < r.x);
}
int cross(point sp, point ep, point op)
{
return (sp.x - op.x) * (ep.y - op.y) - (ep.x - op.x) * (sp.y - op.y);
}
bool cmp(point a, point b)
{
int ans = cross(a, b, base);
if (ans == 0)
return dis(base, a) > dis(base, b);
else
return ans > 0;
}
int graham(point pnt[], int n, point res[])
{
int i, len, top = 1;
sort(pnt, pnt + n);
if (n == 0)
{
return 0;
}
res[0] = pnt[0];
if (n == 1)
{
return 1;
}
res[1] = pnt[1];
if (n == 2)
{
return 2;
}
res[2] = pnt[2];
for (i = 2; i < n; i++)
{
while (top && mult(pnt[i], res[top], res[top - 1]))
{
top--;
}
res[++top] = pnt[i];
}
len = top;
res[++top] = pnt[n - 2];
for (i = n - 3; i >= 0; i--)
{
while (top != len && mult(pnt[i], res[top], res[top - 1]))
{
top--;
}
res[++top] = pnt[i];
}
return top; // 返回凸包中点的个数
}
// --------------以上均为板子----------------------
int main()
{
int n;
while (cin >> n)
{
// 逆时针排序
for (int i = 0; i < n; i++)
{
cin >> p[i].x >> p[i].y;
}
base = p[0];
int pos = 0;
for (int i = 1; i < n; i++)
{
if (p[i].y < base.y || (p[i].y == base.y && p[i].x < base.x))
{
base = p[i];
pos = i;
}
swap(p[0], p[pos]);
}
sort(p + 1, p + n, cmp);
int num = graham(p, n, ans);
// 三重循环枚举三角形更新答案
double S = 0;
for (int i = 0; i < num; i++)
{
for (int j = i + 1; j < num; j++)
{
for (int k = j + 1; k < num; k++)
{
S = max(S, 1.0 * cross(ans[i], ans[j], ans[k]));
}
}
}
cout << fixed << setprecision(2) << S / 2.0 << endl;
}
return 0;
}
1025:Surround the Trees(凸包周长)
同 1022, 又重题了喂。
三、hard
1014:Watch out the Animal(凸包+模拟退火)
题意:动物园中有 个休息点 ,每两个休息点之间有一条监视线。动物有可能越狱,当动物越过最外侧的监视线时视为越狱成功。现在需要在动物园中选择一个点建立信息中心,选择与若干个休息点建立联系,建立联系的代价即两者之间的距离。现在问如果在最低代价的条件下选择一个点建立信息中心并建立联系,使得所有动物越狱成功的消息都可以被中心接收到。
范围:
分析:动物有可能从这个多边形的任意一条边越狱成功,所以我们需要知道所有的越狱消息,必定要让信息中心与所有外侧的点,即凸包上的点建立联系。而本题还有代价最小的条件,即信息中心到凸包上所有点的距离之和尽可能小,换言之题目要我们求的就是多边形的费马点。
对于三角形、四边形的费马点的求解是比较容易的,但是当推广到 边形的时候,问题就变得复杂了起来。
这里我们使用有点像搜索的算法——模拟退火,也叫随机化贪心。
大意就是在多边形内部随机选择一个点,每次以一个长度为 的步长向上下左右四个方向尝试走一步,查看是否成为更优解,是则更新答案。如果当前步长下已经不能得到更优解,那么就缩短步长为一半,知道精度满足题目的要求。
其实也不是特别难,详见代码。
Code:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 100 + 10;
// --------------以下均为板子----------------------
struct point
{
double x, y;
} p[MAXN], ans[MAXN], base;
bool mult(point sp, point ep, point op)
{
return (sp.x - op.x) * (ep.y - op.y) >= (ep.x - op.x) * (sp.y - op.y);
}
double dis(point a, point b)
{
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
inline bool operator<(const point &l, const point &r)
{
return l.y < r.y || (l.y == r.y && l.x < r.x);
}
int graham(point pnt[], int n, point res[])
{
int i, len, top = 1;
sort(pnt, pnt + n);
if (n == 0)
{
return 0;
}
res[0] = pnt[0];
if (n == 1)
{
return 1;
}
res[1] = pnt[1];
if (n == 2)
{
return 2;
}
res[2] = pnt[2];
for (i = 2; i < n; i++)
{
while (top && mult(pnt[i], res[top], res[top - 1]))
{
top--;
}
res[++top] = pnt[i];
}
len = top;
res[++top] = pnt[n - 2];
for (i = n - 3; i >= 0; i--)
{
while (top != len && mult(pnt[i], res[top], res[top - 1]))
{
top--;
}
res[++top] = pnt[i];
}
return top; // 返回凸包中点的个数
}
// --------------以上均为板子----------------------
int num;
// 计算该点到凸包上所有点的距离之和
double calc(point a)
{
double sum = 0;
for (int i = 0; i < num; i++)
{
sum += dis(a, ans[i]);
}
return sum;
}
int main()
{
int T;
cin >> T;
while (T--)
{
int n;
cin >> n;
for (int i = 0; i < n; i++)
{
cin >> p[i].x >> p[i].y;
}
num = graham(p, n, ans);
// 设置凸包上第一个点为初始点
point now = ans[0];
double res = calc(now), step = 100;
// 本题的精度只要整数就可以,所以步长限制0.2足够
while (step > 0.2)
{
// 标记是否有更优解
int update = 1;
// 有更优解则持续进行
while (update)
{
update = 0;
point temp;
double len;
// 上
temp = {now.x, now.y + step};
len = calc(temp);
if (len < res)
{
res = len;
update = 1;
now = temp;
}
// 下
temp = {now.x, now.y - step};
len = calc(temp);
if (len < res)
{
res = len;
update = 1;
now = temp;
}
// 右
temp = {now.x + step, now.y};
len = calc(temp);
if (len < res)
{
res = len;
update = 1;
now = temp;
}
// 左
temp = {now.x - step, now.y};
len = calc(temp);
if (len < res)
{
res = len;
update = 1;
now = temp;
}
}
// 当前步长下没有更优解则更新步长
step /= 2;
}
// 注意题目要求四舍五入
cout << (int)(res + 0.5) << endl;
if (T)
cout << endl;
}
return 0;
}
1018:Rotational Painting(凸包+重心)
题意:给 个点 ,现在将这个多边形立起来放在桌上,问有多少种稳定的放置方案。
范围:, 和 都是浮点数
分析:首先我们需要知道能够稳定放置的条件,就是多边形的重心落在支撑线上。对于重心的求解直接套板子即可,至于支撑线,我们知道这个物体放置在桌上只可能是最外侧的点与桌面进行接触,因此支撑线就是外侧点之间的连线,因此我们需要求出多边形的凸包。
这样我们就枚举所有凸包上的线,判断重心是否在线上(不包括端点),这样就可以判断以这条线作为支撑线是否能够稳定放置。
至于这道题为什么是 hard 呢,因为除了上面的思路,本题很考验细心与耐心,有一点点的小问题可能都要调上一整天,且不说 WA,可能连样例都调不出来!(别问我是怎么知道的)
Code:
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>
using namespace std;
const double eps = 1e-8;
struct point
{
double x, y;
point operator-(const point &t) const
{
point tmp;
tmp.x = x - t.x;
tmp.y = y - t.y;
return tmp;
}
point operator+(const point &t) const
{
point tmp;
tmp.x = x + t.x;
tmp.y = y + t.y;
return tmp;
}
bool operator==(const point &t) const
{
return fabs(x - t.x) < eps && fabs(y - t.y) < eps;
}
} p[100010];
struct line
{
point a, b;
};
int top;
bool cmpxy(point a, point b)
{
if (a.y == b.y)
return a.x < b.x;
return a.y < b.y;
}
double cross(point a, point b, point c)
{
return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y);
}
void tubao(point *p, int n)
{
if (n < 3)
return;
int i, m = 0;
top = 1;
sort(p, p + n, cmpxy);
for (i = n; i < 2 * n - 1; i++)
p[i] = p[2 * n - 2 - i];
for (i = 2; i < 2 * n - 1; i++)
{
while (top > m && cross(p[top], p[i], p[top - 1]) < eps)
top--;
p[++top] = p[i];
if (i == n - 1)
m = top;
}
}
point gravi(point *p, int n)
{
int i;
double A = 0, a;
point t;
t.x = t.y = 0;
p[n] = p[0];
for (i = 0; i < n; i++)
{
a = p[i].x * p[i + 1].y - p[i + 1].x * p[i].y;
t.x += (p[i].x + p[i + 1].x) * a;
t.y += (p[i].y + p[i + 1].y) * a;
A += a;
}
t.x /= A * 3;
t.y /= A * 3;
return t;
}
point intersection(point u1, point u2, point v1, point v2)
{
point ret = u1;
double t = ((u1.x - v1.x) * (v1.y - v2.y) - (u1.y - v1.y) * (v1.x - v2.x)) / ((u1.x - u2.x) * (v1.y - v2.y) - (u1.y - u2.y) * (v1.x - v2.x));
ret.x += (u2.x - u1.x) * t;
ret.y += (u2.y - u1.y) * t;
return ret;
}
point ptoline(point p, point l1, point l2)
{
point t = p;
t.x += l1.y - l2.y, t.y += l2.x - l1.x;
return intersection(p, t, l1, l2);
}
bool dot_onseg(point p, point s, point e)
{
if (p == s || p == e)
return false;
return cross(p, s, e) < eps &&
(p.x - s.x) * (p.x - e.x) < eps && (p.y - s.y) * (p.y - e.y) < eps;
}
int main()
{
int t, i, j, n;
scanf("%d", &t);
while (t--)
{
scanf("%d", &n);
for (i = 0; i < n; i++)
scanf("%lf%lf", &p[i].x, &p[i].y);
point cen = gravi(p, n);
tubao(p, n);
line seg;
point m;
int count = 0;
for (i = 0; i < top; i++)
{
m = ptoline(cen, p[i], p[i + 1]);
if (dot_onseg(m, p[i], p[i + 1]))
count++;
}
printf("%d\n", count);
}
return 0;
}
1020:Maple trees(凸包+最小圆覆盖)
题意:给 棵直径为 的树位于 ,求把所有树包含在其中的最小圆半径是多少。
范围:
分析:最开始的简单想法就是求这个凸包的直径,那么直接上旋转卡壳的板子就可以了,但是发现这个想法假了,凸包上的最小覆盖圆直径并不是凸包的直径,而是有专门的最小圆覆盖算法。
直接求解凸包上所有点的最小圆覆盖不好算,可以把问题分解成小问题来解决。最小圆覆盖算法也正是这么做的,把凸包上所有点的最小覆盖圆问题转换成求解三角形的最小覆盖圆问题。
凸包最小覆盖圆可以由凸包上点所形成的三角形的最小覆盖圆取最大值得到,因为本道题目的数据范围 只有 ,允许使用 的算法,因此我们可以三重循环枚举凸包上的所有点构造三角形来求最小覆盖圆。但是实际上最小覆盖圆算法的时间复杂度的期望为 ,有兴趣的同学可以去了解一下。
那么现在问题就转化为求解三角形的最小覆盖圆。
分类讨论:
① 锐角三角形
根据正弦定理,,且有面积公式 ,联立得到 。
三边都可以通过距离直接求出来,而面积 可以利用叉积先求出平行四边形的面积再除以二得到。
② 直角/钝角三角形
此时三角形的最小覆盖圆不是外接圆,而是以最长边为直径的圆,此时 为最长边的一半。
那么样子所有的问题都解决了,还要注意每棵树都有自己的直径,所有计算最后答案的时候需要加上 ,详见代码。
Code:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1000 + 10;
// --------------以下均为板子----------------------
struct point
{
double x, y;
} points[MAXN], ans[MAXN], base;
bool mult(point sp, point ep, point op)
{
return (sp.x - op.x) * (ep.y - op.y) >= (ep.x - op.x) * (sp.y - op.y);
}
int cross(point sp, point ep, point op)
{
return (sp.x - op.x) * (ep.y - op.y) - (ep.x - op.x) * (sp.y - op.y);
}
double dis(point a, point b)
{
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
inline bool operator<(const point &l, const point &r)
{
if (cross(l, r, base) == 0)
{
return dis(base, l) < dis(base, r);
}
else if (cross(l, r, base) > 0)
{
return true;
}
return false;
}
bool cmp(point l, point r)
{
return l.y < r.y || (l.y == r.y && l.x < r.x);
}
int graham(point pnt[], int n, point res[])
{
int i, len, top = 1;
sort(pnt, pnt + n, cmp);
if (n == 0)
{
return 0;
}
res[0] = pnt[0];
if (n == 1)
{
return 1;
}
res[1] = pnt[1];
if (n == 2)
{
return 2;
}
res[2] = pnt[2];
for (i = 2; i < n; i++)
{
while (top && mult(pnt[i], res[top], res[top - 1]))
{
top--;
}
res[++top] = pnt[i];
}
len = top;
res[++top] = pnt[n - 2];
for (i = n - 3; i >= 0; i--)
{
while (top != len && mult(pnt[i], res[top], res[top - 1]))
{
top--;
}
res[++top] = pnt[i];
}
return top; // 返回凸包中点的个数
}
// --------------以上均为板子----------------------
// 判断最长边为c的三角形是否为钝角三角形
int check(double a, double b, double c)
{
return a * a + b * b < c * c;
}
// 计算三角形abc的最小覆盖圆
double calc(point a, point b, point c)
{
double p0p1 = dis(a, b);
double p0p2 = dis(a, c);
double p1p2 = dis(b, c);
// 如果是直角/钝角
if (check(p0p1, p0p2, p1p2) || check(p0p1, p1p2, p0p2) || check(p1p2, p0p2, p0p1))
{
return max(p0p1, max(p0p2, p1p2)) / 2.0;
}
// 如果是锐角
double S = fabs(mult(b, c, a)) / 2.0;
return p0p1 * p0p2 * p1p2 / 4.0 / S;
}
int main()
{
int n;
while (cin >> n, n)
{
// 完成逆时针排序
cin >> points[0].x >> points[0].y;
base = points[0];
int pos = 0;
for (int i = 1; i < n; i++)
{
cin >> points[i].x >> points[i].y;
if (points[i].y < base.y || (points[i].y == base.y && points[i].x < base.x))
{
base = points[i];
pos = i;
}
}
swap(points[0], points[pos]);
sort(points + 1, points + n);
// 需要特判一个点和两个点的情况
if (n == 1)
{
cout << "0.50" << endl;
continue;
}
int num = graham(points, n, ans);
if (num == 2)
{
double res = dis(ans[1], ans[0]) / 2.0 + 0.5;
cout << fixed << setprecision(2) << res << endl;
continue;
}
// 三重循环枚举凸包上的所有三角形更新答案
double res = 0;
for (int i = 0; i < num; i++)
{
for (int j = i + 1; j < num; j++)
{
for (int k = j + 1; k < num; k++)
{
res = max(res, calc(ans[i], ans[j], ans[k]));
}
}
}
cout << fixed << setprecision(2) << res + 0.5 << endl;
}
return 0;
}
1026:Quoit Design(二维平面最近点对,经典分治)
第四周的贪心场已经做过了,经典的分治问题。
有兴趣的可以去看看我第四周的题解第 1015 题。
四、超纲
这些题除了 1013 都是2018CCPC网络赛的题目,难度大,范围广,不是我们现在阶段需要考虑的题目,这里放出题解,有兴趣的同学可以看看(这种难度的题目,什么单调队列优化DP,线段树DP,网络流…真的会有人看吗?1013甚至都没有题解!)
【END】感谢观看