本文涉及知识点
计算几何
P6732 「Wdsr-2」方分
题目描述
平面直角坐标系上有两个相离的正方形 A A A 和 B B B。请找到任意一条直线把它们分隔到直线的两侧。
注意:这条直线不应该和正方形有公共点。
输入格式
输入包含多组数据。
第一行输入一个正整数 T T T ,代表数据组数。
对于每组数据,共有 8 8 8 行,每行 2 2 2 个实数,描述一个点的 x x x 轴坐标和 y y y 轴坐标。前四行表示正方形 A A A 四个顶点的坐标,后四行表示正方形 B B B 四个顶点的坐标。
保证这四个顶点坐标一定能构成一个正方形,但顺序可以是任意的。两个正方形一定是相离的。
没有保证正方形的边和坐标轴平行!
输出格式
对于每组数据,输出一行三个实数 a , b , c a,b,c a,b,c,表示你找到的直线方程是 a x + b y = c ax+by=c ax+by=c。
输入输出样例 #1
输入 #1
1
1.0 1.0
2.0 2.0
1.0 2.0
2.0 1.0
0.0 0.0
-0.5 -0.5
0.0 -1.0
0.5 -0.5
输出 #1
0.0 1.0 0.5
说明/提示
1 ≤ T ≤ 10000 1\le T\le 10000 1≤T≤10000。
输入的坐标绝对值不超过 1 0 3 10^3 103,小数点后最多有 3 3 3 位数字。
SPJ 使用双精度浮点数计算验证你的答案,请尽可能地避免精度误差。
避免精度误差的方式有:不要输出绝对值过大或过小的数字,输出尽可能多的小数点后位数,比较大小时使用 eps,等等。
赛后已加入 Hack 数据。
计算几何
SAT轴:两个相离的凸多边形,一定能找到直线将两者分开,且这条直线平行于两个多边形的某条边。
第一层循环:枚举各边。
第二循环:此边的法线是 v ⃗ \vec v v ,令第一个凸多边形在 v ⃗ \vec v v 的投影最小值最大值分别为x1,x2,第二个凸多边在 v ⃗ \vec v v 的投影值分别为x3,x4。如果x2 < x3或x4 < x1,有解。
不失一般性,令x2<x3。则:x23= ( x 2 + x 3 ) ÷ 2 (x2+x3)\div 2 (x2+x3)÷2。 P = x 23 v ⃗ P = x23 \vec v P=x23v ,分离的直线就是经过P,平行于边的直线。
点P 到向量 v ⃗ \vec v v 的投影。
∣ O P ∣ 的投影 ∣ v |OP|的投影 |v ∣OP∣的投影∣v|=点乘
实现
令矩形的4个顶点是任意顺序是ABCD。BCD中距离A最远的点和C交换。交换后,ABCD是顺时针或逆时针。
单元测试
性质一 :如果一条线段两个端点在直线一则,则整体线段在直线一侧。证明见:
【计算几何】凸多变形的定义。
性质二 :凸多边形所有端点都在一侧,则整个多变形都在一侧。根据性质一,边界上的点都在一侧。令多边形内一任意一点,做向右水平射线,和多变形交于A;向左做水平射线,和多变形交于B点。根据性质一,AB都在直线一侧,故此点也在这一侧。
故单元测试直接比较正方形各端点到直线的有向距离,是否一个正方形全正,一个正方形全负。 点到直线距离,直线可以用一般形式。
代码
hack数据没过,所以100分只能得99分。可能是精度得问题,乘以2000再转成整数也许可行。
cpp
#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include<list>
#include <bitset>
using namespace std;
template<class T1, class T2>
std::istream& operator >> (std::istream& in, pair<T1, T2>& pr) {
in >> pr.first >> pr.second;
return in;
}
template<class T1, class T2, class T3 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3>& t) {
in >> get<0>(t) >> get<1>(t) >> get<2>(t);
return in;
}
template<class T1, class T2, class T3, class T4 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4>& t) {
in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t);
return in;
}
template<class T = int>
vector<T> Read() {
int n = 0;
cin >> n;
vector<T> ret(n);
for (int i = 0; i < n; i++) {
cin >> ret[i];
}
return ret;
}
template<class T = int>
vector<T> Read(int n) {
vector<T> ret(n);
for (int i = 0; i < n; i++) {
cin >> ret[i];
}
return ret;
}
template<int N = 1'000'000>
class COutBuff
{
public:
COutBuff() {
m_p = puffer;
}
template<class T>
void write(T x) {
int num[28], sp = 0;
if (x < 0)
*m_p++ = '-', x = -x;
if (!x)
*m_p++ = 48;
while (x)
num[++sp] = x % 10, x /= 10;
while (sp)
*m_p++ = num[sp--] + 48;
AuotToFile();
}
inline void write(char ch)
{
*m_p++ = ch;
AuotToFile();
}
inline void ToFile() {
fwrite(puffer, 1, m_p - puffer, stdout);
m_p = puffer;
}
~COutBuff() {
ToFile();
}
private:
inline void AuotToFile() {
if (m_p - puffer > N - 100) {
ToFile();
}
}
char puffer[N], * m_p;
};
template<int N = 12 * 1'000'000>
class CInBuff
{
public:
inline CInBuff() {
fread(buffer, 1, N, stdin);
}
inline int Read() {
int x(0), f(0);
while (!isdigit(*S))
f |= (*S++ == '-');
while (isdigit(*S))
x = (x << 1) + (x << 3) + (*S++ ^ 48);
return f ? -x : x;
}
private:
char buffer[N], * S = buffer;
};
template<class T>
class CVector2D;
template<class T>
class CPoint2D {
public:
T X, Y;
// 构造函数
CPoint2D() : X(0), Y(0) {}
CPoint2D(T x, T y) : X(x), Y(y) {}
// 两点距离
T Distance(const CPoint2D<T>& other) const {
T dx = X - other.X;
T dy = Y - other.Y;
return std::sqrt(dx * dx + dy * dy);
}
// 向量运算
CVector2D<T> operator-(const CPoint2D<T>& other) const {
return CVector2D<T>(X - other.X, Y - other.Y);
}
CPoint2D<T> operator+(const CVector2D<T>& vec) const {
return CPoint2D<T>(X + vec.X, Y + vec.Y);
}
CPoint2D<T> operator-(const CVector2D<T>& vec) const {
return CPoint2D<T>(X - vec.X, Y - vec.Y);
}
};
template<class T>
class CVector2D {
public:
T X, Y;
// 构造函数
CVector2D() : X(0), Y(0) {}
CVector2D(T x, T y) : X(x), Y(y) {}
CVector2D(const CPoint2D<T>& from, const CPoint2D<T>& to)
: X(to.X - from.X), Y(to.Y - from.Y) {}
// 点积
T DotMul(const CVector2D<T>& other) const {
return X * other.X + Y * other.Y;
}
// 叉积(二维叉积返回标量)
T CrossMul(const CVector2D<T>& other) const {
return X * other.Y - Y * other.X;
}
// 与点的点积(常用于点到向量投影)
T DotMul(const CPoint2D<T>& point) const {
return X * point.X + Y * point.Y;
}
// 从两点创建向量(静态方法)
static CVector2D<T> From2Point(const CPoint2D<T>& ptFrom, const CPoint2D<T>& ptTo) {
return CVector2D<T>(ptTo.X - ptFrom.X, ptTo.Y - ptFrom.Y);
}
// 向量长度
T Len() const {
return std::sqrt(X * X + Y * Y);
}
// 单位向量
CVector2D<T> Normalized() const {
T length = Len();
if (length == 0) return CVector2D<T>(0, 0);
return CVector2D<T>(X / length, Y / length);
}
/// <summary>
/// 以 ptOrigin 为原点,xAxis 为 X 轴正方向建立新坐标系,
/// 返回点 pt 在新坐标系中的坐标
/// </summary>
static CPoint2D<T> PositionInNewCoord(const CPoint2D<T>& pt,
const CPoint2D<T>& ptOrigin,
const CVector2D<T>& xAxis) {
T len = xAxis.Len();
if (len == 0) {
// 处理零向量情况
return CPoint2D<T>(0, 0);
}
CVector2D<T> v = From2Point(ptOrigin, pt);
T dDot = v.DotMul(xAxis);
T dCross = v.CrossMul(xAxis);
return CPoint2D<T>(dDot / len, dCross / len);
}
// 向量运算
CVector2D<T> operator+(const CVector2D<T>& other) const {
return CVector2D<T>(X + other.X, Y + other.Y);
}
CVector2D<T> operator-(const CVector2D<T>& other) const {
return CVector2D<T>(X - other.X, Y - other.Y);
}
CVector2D<T> operator*(T scalar) const {
return CVector2D<T>(X * scalar, Y * scalar);
}
CVector2D<T> operator/(T scalar) const {
return CVector2D<T>(X / scalar, Y / scalar);
}
};
class Solution {
public:
tuple<double, double, double> Ans(vector<double>& xys) {
MakeOrder(xys.data());
MakeOrder(xys.data() + 8);
const double* p = xys.data();
CVector2D<double> v[8];
for (int i = 0; i < 4; i++) {
v[i].X = p[2 * i] - p[(2 * i + 2) % 8];
v[i].Y = p[2 * i + 1] - p[(2 * i + 2) % 8 + 1];
v[i + 4].X = p[2 * i + 8] - p[(2 * i + 2) % 8 + 8];
v[i + 4].Y = p[2 * i + 1 + 8] - p[(2 * i + 2) % 8 + 1 + 8];
}
double a, b, c;
double xs[8];
double minLen = 0;
for (int i = 0; i < 8; i++) {
CVector2D<double> nv = CVector2D<double>(v[i].Y, -v[i].X).Normalized();
for (int j = 0; j < 8; j++) {
CVector2D<double> v1(p[j * 2], p[j * 2 + 1]);
xs[j] = v1.DotMul(nv);
}
const double min1 = *min_element(xs + 0, xs + 4);
const double max1 = *max_element(xs + 0, xs + 4);
const double min2 = *min_element(xs + 4, xs + 8);
const double max2 = *max_element(xs + 4, xs + 8);
const double len = min(max1, max2) - max(min1, min2);//如果是正数,两个正方形在法向量投影重叠长度;如果是负数,其绝对值是两段投影的距离。
if (len < minLen) {
minLen = len;
CVector2D<double> cen = nv * (max(min1, min2) + len / 2);
a = nv.X, b = nv.Y;
c = (a * cen.X + b * cen.Y);
}
}
return make_tuple(a, b, c);
}
void MakeOrder(double* p) {
double dis[4] = { 0 };
auto Dis = [&](double x, double y) {
return sqrt((x - p[0]) * (x - p[0]) + (y - p[1]) * (y - p[1]));
};
for (int i = 1; i < 4; i++) {
dis[i] = Dis(p[i * 2], p[i * 2 + 1]);
}
int inx = max_element(dis + 1, dis + 4) - dis;
swap(p[inx * 2], p[4]);
swap(p[inx * 2 + 1], p[5]);
}
};
int main() {
#ifdef _DEBUG
freopen("a.in", "r", stdin);
#endif // DEBUG
ios::sync_with_stdio(0); cin.tie(nullptr);
int n = 0;
cin >> n;
while (n--)
{
auto xys = Read<double>(16);
#ifdef _DEBUG
//printf("B=%lf", B);
//Out(que, "que=");
Out(xys, "xys=");
//Out(P, ",P=");
#endif // DEBUG
auto [a,b,c] = Solution().Ans(xys);
//printf("%.4lf", res);
printf("%.12lf %.12lf %.12lf\n", a, b, c);
}
return 0;
};
单元测试
cpp
void Check(vector<double> xys, double a, double b, double c) {
double len = sqrt(a * a + b * b);
a /= len; b /= len; c /= len;
double dis[8] = { 0 };
for (int i = 0; i < 8; i++) {
dis[i] = a * xys[i * 2] + b * xys[i * 2 + 1] - c;
}
const double min1 = *min_element(dis + 0, dis + 4);
const double max1 = *max_element(dis + 0, dis + 4);
const double min2 = *min_element(dis + 4, dis + 8);
const double max2 = *max_element(dis + 4, dis + 8);
const bool b1 = (min1 > 0) && (max2 < 0);
const bool b2 = (max1 < 0) && (min2 > 0);
Assert::IsTrue(b1 || b2);
}
vector<double> xys;
TEST_METHOD(TestMethod11)
{
xys = { 1,1,2,2,1,2,2,1,0,0,-0.5,-0.5,0,-1,0.5,-0.5 };
auto [a,b,c] = Solution().Ans(xys);
//AssertEx(make_tuple(0.0,1.0,0.5), res);
Check(xys, 0, 1, 0.5);
Check(xys, a, b, c);
}

扩展阅读
| 我想对大家说的话 |
|---|
| 工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
| 学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作 |
| 有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
| 员工说:技术至上,老板不信;投资人的代表说:技术至上,老板会信。 |
| 闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
| 子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
| 如果程序是一条龙,那算法就是他的是睛 |
| 失败+反思=成功 成功+反思=成功 |
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法 用**C++**实现。
