【计算几何 SAT轴】P6732 「Wdsr-2」方分|普及+

本文涉及知识点

数学

计算几何

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++**实现。

相关推荐
embrace992 小时前
【C语言学习】预处理详解
java·c语言·开发语言·数据结构·c++·学习·算法
拼好饭和她皆失2 小时前
《二分答案算法精讲:从原理到实战(上篇)》
c++·算法
helloworddm2 小时前
C++与C#交互 回调封装为await
c++·c#·交互
应用市场2 小时前
TCP网络连接断开检测机制详解——C++实现网络连通性判断与断线类型识别
网络·c++·tcp/ip
雾岛听蓝2 小时前
C/C++内存管理
c语言·c++
AuroraWanderll2 小时前
类和对象(三)-默认成员函数详解与运算符重载
c语言·开发语言·数据结构·c++·算法
Minecraft红客2 小时前
C++制作迷宫第一版
c++·游戏·电脑·娱乐
雪域迷影2 小时前
Windows11中VS2026使用C++ 现代化json库nlohmann的3种方式
开发语言·c++·json
羑悻的小杀马特2 小时前
LRU Cache:高频访问数据的“智能保鲜舱”与经典淘汰艺术
c++·后端·lru cache·热点数据与冷数据