【计算几何 化环为链】P14165 [ICPC 2022 Nanjing R] 清空水箱|普及+

本文涉及知识点

数学

计算几何 化环为链

P14165 [ICPC 2022 Nanjing R] 清空水箱

题目描述

自来水厂最近建造了一种多边形水箱,水箱的厚度可以忽略不计。

为了启用水箱,工程师们准备在水箱上安装若干出水阀门。一个出水阀门可以被看作水箱上的一个点,当阀门打开时,水箱里的水会从阀门流出。

:::align{center}

:::

如上图所示,紫色的点代表阀门,而浅蓝色区域代表阀门全部打开后水箱内剩余的水。

作为总工程师的您需要知道,至少需要安装多少个出水阀门,才能在所有阀门同时打开后,让水箱里的水全部流出。

您可以认为水是一种理想流体且环境中不存在大气压,因此水总有流向更低处的趋势,即使位于水平平面上也是如此。

输入格式

每个测试文件仅有一组测试数据。

第一行输入一个整数 n n n( 3 ≤ n ≤ 2 × 10 3 3 \le n \le 2 \times 10^3 3≤n≤2×103)表示多边形(也就是水箱的形状)的顶点数。

对于接下来 n n n 行,第 i i i 行输入两个整数 x i x_i xi 和 y i y_i yi( ∣ x i ∣ , ∣ y i ∣ ≤ 10 4 |x_i|, |y_i| \le 10^4 ∣xi∣,∣yi∣≤104)表示多边形第 i i i 个顶点的坐标。顶点按逆时针顺序给出。

给定的多边形是简单多边形。也就是说,多边形的顶点两两不同,且除了相邻边存在公共顶点外,不存在两条边有公共点。 不 \textbf{\LARGE{不}} 不保证相邻边不共线。

输出格式

输出一行一个整数表示清空水箱至少需要多少个出水阀门。

输入输出样例 #1

输入 #1

复制代码
6
0 0
1 1
2 1
3 0
3 2
0 2

输出 #1

复制代码
2

输入输出样例 #2

输入 #2

复制代码
8
4 4
0 4
0 2
1 2
2 2
2 0
3 0
4 0

输出 #2

复制代码
1

输入输出样例 #3

输入 #3

复制代码
7
1 0
3 4
0 3
1 2
2 3
1 1
0 2

输出 #3

复制代码
2

说明/提示

对于第一组样例数据,在 ( 0 , 0 ) (0,0) (0,0) 与 ( 3 , 0 ) (3,0) (3,0) 安装两个出水阀门即可清空水箱。

对于第二组样例数据,在 ( 3 , 0 ) (3,0) (3,0) 安装一个出水阀门即可清空水箱。事实上,只要把该出水阀门安装在满足 2 ≤ x ≤ 4 2 \le x \le 4 2≤x≤4 的 ( x , 0 ) (x, 0) (x,0) 即可。

对于第三组样例数据,在 ( 1 , 0 ) (1,0) (1,0) 与 ( 1 , 2 ) (1,2) (1,2) 安装两个出水阀门即可清空水箱。

:::align{center}

计算几何

令相邻三点依次为A(x0,y0),B(x1,y1),C(x2,y2)。

情况一:如果y0<y1或y2<y1。则(x1,y1)的水一定可以流向别处,故无需在(x1,y1)处开口。如果y1<y0且y1<y2,如果y1在上边界,则无需开孔。即逆时针旋转需要开孔。

情况二:如果 y 0 = y 1 = y 2 y0=y1=y2 y0=y1=y2,则三点共线。可预处理时,将三点共水平线排除掉。

情况三:A(x0,y0),B(x1,y1),C(x2,y1),D(x3,y3)。如果y0或y3小于y1,则(x1,y1)和(x2,y1)不需要开孔。如果 A B ⃗ B C ⃗ \vec{AB} \vec{BC} AB BC 逆时针旋转则是下端点,需要开孔。

不严谨的证明

如果y1<y0,y2<y0。则 △ A B C \triangle ABC △ABC中的水,都会流向B。

根据高斯面积公式,ABC的有向面积为正,则多边形内。即B必须开孔,否则 △ A B C \triangle ABC △ABC中的中的水,无法流出。开在 ∠ A B C \angle ABC ∠ABC其它处,B处的水无法流出。 △ A B C \triangle ABC △ABC的有向面积为负,在多边形外,故无需开孔。ABCD可以拆分ABC,BCD。

处理相邻边水平共线(错误解法)

条件改成 y 0 > y 1 且 y 1 ≤ y 2 且 A B ⃗ × B C ⃗ > 0 y0 > y1 且 y1 \le y2 且\vec{AB}\times \vec{BC} >0 y0>y1且y1≤y2且AB ×BC >0,这样只会枚举B,不会枚举C。也不会枚举BC间的点。

cpp 复制代码
class Solution {
		public:
			int Ans(vector<pair<int, int>>& pts) {
				const int N = pts.size();
				pts.emplace_back(pts[0]);
				pts.emplace_back(pts[1]);
				int ans = 0;
				for (int i = 1; i <= N; i++) {
					if ((pts[i - 1].second > pts[i].second) && (pts[i + 1].second >= pts[i].second)){
						int iCorss = CrossMul(pts[i-1],pts[i],pts[i+1]);
						ans += (iCorss > 0);
					}
				}
				return ans;
			}
		};

化环为链

BC共线时,A.Y和D.Y必须大于B.Y=C.Y。 A B ⃗ × B C ⃗ > 0 \vec{AB} \times \vec{BC}>0 AB ×BC >0。

ABC且BC平行无需特殊处理:D就是A,故A.Y=D.Y > B.Y。
注意:先处理水平共线,再处理化环为链。

化环为链

i =0 to n-1枚举第一个点。

增加pts[i]到pts2时,如果pts2.back()和pts2倒数第二个元素和pts[i]y相同。则pts.popback()。这样可以扔掉水平共线的中间的点。

再处理链头和链尾。令A=pts2[0] B=pts2[1] C = pts2倒数第二个 D = pts2.back()。

如果A.Y != D.Y,则头尾没有水平共线。CDAB

cnt = (A.Y == B.Y)+(A.Y==C.Y)

cnt=0无共线

cnt1 CDA或DAB共线。 删除D或A。

cnt2 四个点共线。删除DA。

代码

核心代码

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 T1, class T2, class T3, class T4,class T5 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4,T5>& t) {
	in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t) >> get<4>(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>
T CrossMul(T x1, T y1, T x2, T y2) {
	return x1 * y2 - x2 * y1;
}
template<class T>
T CrossMul(const pair<T, T>& pr1, const pair<T, T>& pr2, const pair<T, T>& pr3) {
	return CrossMul(pr2.first - pr1.first, pr2.second - pr1.second, pr3.first - pr2.first, pr3.second - pr2.second);
}

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);
	}
	bool operator<(const CPoint2D<T>& o) const {
		if (X == o.X) { return Y < o.Y; }
		return X < o.X;
	};
};

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:
			int Ans(vector<pair<int, int>>& pts) {					
				Change(pts);
				const int N = m_pts.size();
				m_pts.emplace_back(m_pts[0]);
				m_pts.emplace_back(m_pts[1]);
				m_pts.emplace_back(m_pts[2]);
				int ans = 0;
				for (int i = 0; i < N; i++) {
					auto pt0 = m_pts[i], pt1 = m_pts[i + 1], pt2 = m_pts[i + 2], pt3 = m_pts[i + 3];
					const int iCross = CrossMul(pt0, pt1, pt2);
					if (iCross <= 0) { continue; }
					if ((pt0.second > pt1.second) && (pt2.second > pt1.second)) {
						ans++;
					}
					if ((pt0.second > pt1.second) && (pt1.second == pt2.second) && (pt3.second > pt1.second)) {
						ans++;
					}
				}
				return ans;
			}
			void Change(vector<pair<int, int>>& pts1) {
				for (const auto& pt : pts1) {
					if ((m_pts.size() >= 2) && (m_pts.back().second == pt.second) && (m_pts[m_pts.size() - 2].second == pt.second)) {
						m_pts.pop_back();
					}
					m_pts.emplace_back(pt);
				}
				if (m_pts.size() <= 3) { return; }
				if (m_pts[0].second != m_pts.back().second) { return; }
				int cnt =(m_pts.back().second == m_pts[m_pts.size() - 2].second);
				cnt += (m_pts.back().second == m_pts[1].second);
				while (cnt--) {
					m_pts.pop_back();
				}
			}
			vector<pair<int, int>> m_pts;
		};

int main() {
#ifdef _DEBUG
	freopen("a.in", "r", stdin);
#endif // DEBUG	
	ios::sync_with_stdio(0); cin.tie(nullptr);
	auto pts = Read<pair<int,int>>();
#ifdef _DEBUG	
		//printf("k=%d", k);
		//Out(que, "que=");
		Out(pts, ",pts=");
		//Out(P, ",P=");
#endif // DEBUG	
		auto res = Solution().Ans(pts);
		cout << res << "\n";
	return 0;
};

单元测试

cpp 复制代码
vector<pair<int, int>> pts;
		TEST_METHOD(TestMethod11)
		{
			pts = { {0,0},{1,1},{2,1},{3,0},{3,2},{0,2} };
			auto res = Solution().Ans(pts);
			AssertEx(2, res);
		}
		TEST_METHOD(TestMethod12)
		{
			pts = { {4,4},{0,4},{0,2},{1,2},{2,2},{2,0},{3,0},{4,0} };
			auto res = Solution().Ans(pts);
			AssertEx(1, res);
		}
		TEST_METHOD(TestMethod13)
		{
			pts = { {1,0},{3,4},{0,3},{1,2},{2,3},{1,1},{0,2} };
			auto res = Solution().Ans(pts);
			AssertEx(2, res);
		}
		TEST_METHOD(TestMethod14)
		{
			pts = { {1,1},{0,0},{3,0} };
			auto res = Solution().Ans(pts);
			AssertEx(1, res);
		}
		TEST_METHOD(TestMethod15)
		{
			pts = {{1,0},{2,0},{2,2}, {-1,0},{0,0} };
			auto res = Solution().Ans(pts);
			AssertEx(1, res);
		}
		TEST_METHOD(TestMethod16)
		{
			pts = {{2,0},{2,2}, {-1,0},{0,0} };
			auto res = Solution().Ans(pts);
			AssertEx(1, res);
		}
		TEST_METHOD(TestMethod17)
		{
			pts = { {1,0},{2,0},{2,2}, {-1,0} };
			auto res = Solution().Ans(pts);
			AssertEx(1, res);
		}

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
员工说:技术至上,老板不信;投资人的代表说:技术至上,老板会信。
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步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++**实现。

相关推荐
爱吃生蚝的于勒2 小时前
【Linux】深入理解软硬链接
linux·运维·服务器·c语言·数据结构·c++·算法
YGGP2 小时前
【Golang】LeetCode 763. 划分字母区间
算法·leetcode
Heath0332 小时前
BGE-M3个人理解
python·算法
Hello.Reader2 小时前
Flink ML Bucketizer 连续特征分桶(多列映射、splitsArray、handleInvalid)+ Java 示例解读
java·算法·flink
先做个垃圾出来………2 小时前
1611. 使整数变为 0 的最少操作次数
算法
ctrigger2 小时前
监理工程师考试题型有哪些?4科题型+分值表
大数据·javascript·算法
YGGP2 小时前
【Golang】LeetCode 1143. 最长公共子序列
算法·leetcode
老王熬夜敲代码2 小时前
C++模版元编程1
数据结构·c++·笔记
Swift社区2 小时前
LeetCode 459 - 重复的子字符串
算法·leetcode·职场和发展