【计算几何 二分查找】P5485 [JLOI2010] 铁人双项比赛|普及+

本文涉及知识点

数学
C++二分查找

计算几何

P5485 [JLOI2010] 铁人双项比赛

题目描述

铁人双项比赛是吉林教育学院的一项传统体育项目。该项目比赛由长跑和骑自行车组成,参赛选手必须先完成 k k k 公里的长跑,然后完成r公里的骑车,才能到达终点。每个参赛选手所擅长的项目不同,有的擅长长跑,有的擅长骑车。如果总赛程 s = k + r s=k+r s=k+r 一定,那么 k k k 越大,对擅长长跑的选手越有利; k k k 越小,对擅长骑车的选手越有利。

现在给定总赛程 s s s,以及每个选手长跑和骑车的平均速度,请你求出对于某个指定的选手最有利的 k k k 和 r r r。所谓最有利,是指选择了这个 k k k 和 r r r 后,该选手可以获得冠军,且领先第 2 2 2 名尽量地多。

输入格式

你的程序从文件读入输入数据。

输入的第一行是两个正整数 s s s 和 n n n, s s s 表示总赛程(单位为公里, s ≤ 2 31 s\leq 2^{31} s≤231), n n n 表示参赛总人数( 2 ≤ n ≤ 100 2\leq n\leq 100 2≤n≤100)。

接下来的 n n n 行每行是两个实数,分别表示每个选手长跑的平均速度和骑车的平均速度(单位为千米/小时)。

第 n n n 个选手就是指定的选手,你的任务是求出对他最有利的 k k k 和 r r r。

输出格式

你的程序的输出包括三个数 k , r , t k,r,t k,r,t,分别表示对第 n n n 号选手最有利的 k k k 和 r r r(浮点数,保留小数点后 2 2 2 位),以及在选择 k k k 和 r r r 的情况下,第 n n n 号选手最多可以领先第 2 2 2 名多少秒(四舍五入到整数);如果另一个选手和该选手并列第一,则 t i = 0 t_i=0 ti=0。倘若无论选择什么 k k k, r r r 都不能使第 n n n 号选手获胜,则输出 NO

输入输出样例 #1

输入 #1

复制代码
100 3
10.0 40.0
20.0 30.0
15.0 35.0

输出 #1

复制代码
14.29 85.71 612

计算几何

令第i位选手,长跑和骑车速度分别为 v 1 i , v 2 i v1_i,v2_i v1i,v2i,令长跑得路程为x,则第i位选手用时为: f i ( x ) = x ÷ v 1 i + ( s − x ) ÷ v 2 i = ( 1 ÷ v 1 i − 1 ÷ v 2 ) x + s ÷ v 2 i f_i(x)=x \div v1_i+(s-x)\div v2_i=(1\div v1_i-1\div v2)x+s\div v2_i fi(x)=x÷v1i+(s−x)÷v2i=(1÷v1i−1÷v2)x+s÷v2i
g i ( x ) g_i(x) gi(x)=第n位选手领先第i位选手得时间数= f i ( x ) − f n ( x ) f_i(x)-f_n(x) fi(x)−fn(x)
( 1 ÷ v 1 i − 1 ÷ v 2 i − 1 ÷ v 1 n + 1 ÷ v 2 n ) x + ( s ÷ v 2 i − s ÷ v 2 n ) (1\div v1_i - 1 \div v2_i - 1 \div v1_n+1 \div v2_n)x+(s\div v2_i-s\div v2_n) (1÷v1i−1÷v2i−1÷v1n+1÷v2n)x+(s÷v2i−s÷v2n)
h 1 ( x ) = min ⁡ ( f i ( x ) ) , g i ( x ) 斜率为正 h1(x)=\min(f_i(x)),g_i(x)斜率为正 h1(x)=min(fi(x)),gi(x)斜率为正
h 2 ( x ) = min ⁡ ( f i ( x ) ) , g i ( x ) 斜率为 0 h2(x)=\min(f_i(x)),g_i(x)斜率为0 h2(x)=min(fi(x)),gi(x)斜率为0
h 3 ( x ) = min ⁡ ( f i ( x ) ) , g i ( x ) 斜率为负 h3(x)=\min(f_i(x)),g_i(x)斜率为负 h3(x)=min(fi(x)),gi(x)斜率为负

显然h1升序,h3降序。

如果h1(s) < h3(s),则k=s。

否则二分查找:最小x,h1(x) ≥ h 3 ( x ) \ge h3(x) ≥h3(x)

ans = h1(x),h2(x),h3(x)得最小值。

如果ans < 0。返回"N0"。
注意

单位一致,如把速度转成 米/秒。x公里每小时,相当于x/3.6米每秒。

1公里相当于1000米。

由于误差原因,0可能被解析为0.00001或-0.00001,后者输出的时候会输出负号。而引起结果不对。

本题有个隐藏要求:任意k都合法,取0。故需要特殊处理:

cpp 复制代码
if (F(s) <= G(s)) {
					if (F(0) == F(s)) {
						return make_tuple(0, s, min(F(0), G(0)));
					}
				}

代码

核心代码

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);
	}
};

template<class INDEX_TYPE>
class CBinarySearch
{
public:
	CBinarySearch(INDEX_TYPE iMinIndex, INDEX_TYPE iMaxIndex, INDEX_TYPE tol = 1) :m_iMin(iMinIndex), m_iMax(iMaxIndex), m_iTol(tol) {}
	template<class _Pr>
	INDEX_TYPE FindFrist(_Pr pr)
	{
		auto left = m_iMin - m_iTol;
		auto rightInclue = m_iMax;
		while (rightInclue - left > m_iTol)
		{
			const auto mid = left + (rightInclue - left) / 2;
			if (pr(mid))
			{
				rightInclue = mid;
			}
			else
			{
				left = mid;
			}
		}
		return rightInclue;
	}
	template<class _Pr>
	INDEX_TYPE FindEnd(_Pr pr)
	{
		INDEX_TYPE leftInclude = m_iMin;
		INDEX_TYPE right = m_iMax + m_iTol;
		while (right - leftInclude > m_iTol)
		{
			const auto mid = leftInclude + (right - leftInclude) / 2;
			if (pr(mid))
			{
				leftInclude = mid;
			}
			else
			{
				right = mid;
			}
		}
		return leftInclude;
	}
protected:
	const INDEX_TYPE m_iMin, m_iMax, m_iTol;
};

class Solution {
		public:
			tuple<double, double, double> Ans(double s, vector<pair<double, double>>& vs) {
				vector<double> k1s, b1s, k2s, b2s;
				const double v1n = vs.back().first / 3.6, v2n = vs.back().second / 3.6;
				for (int i = 0; i + 1 < vs.size(); i++) {
					double v1i = vs[i].first / 3.6, v2i = vs[i].second / 3.6;
					const double k = 1.0 / v1i - 1.0 / v2i - 1.0 / v1n + 1.0 / v2n;
					const double b = (s * 1000) / v2i - (s * 1000) / v2n;
					if (k >= 0) {
						k1s.emplace_back(k); b1s.emplace_back(b);
					}
					else {
						k2s.emplace_back(k); b2s.emplace_back(b);
					}
				}
				auto FG = [&](const vector<double>& ks, const vector<double>& bs, double s1) {
					s1 *= 1000;
					double	dMin = 1.0E307;
					for (int i = 0; i < ks.size(); i++) {
						const auto cur = ks[i] * s1 + bs[i];
						dMin = min(dMin, cur);
					}
					return dMin;
				};
				auto F = [&](double s1) {return FG(k1s, b1s, s1); };
				auto G = [&](double s1) {return FG(k2s, b2s, s1); };
				if (F(s) <= G(s)) {
					if (F(0) == F(s)) {
						return make_tuple(0, s, min(F(0), G(0)));
					}
				}
				auto Check = [&](double mid) {return F(mid) >= G(mid); };
				double s1 = CBinarySearch<double>(0, s, 0.0001).FindFrist(Check);
                if(s1 < 0 ){
                    s1 = 0;
                }
				/*double tmp1 = F(s1);
				double tmp2 = G(s1);
				vector<double> tmp3;
				for (const auto& [v1, v2] : vs) {
					double hour = s1 / v1 + (s - s1) / v2;
					tmp3.emplace_back(hour * 3600);
				}*/
				return make_tuple(s1, s - s1, min(F(s1),G(s1)));
			}
		};

int main() {
#ifdef _DEBUG
	freopen("a.in", "r", stdin);
#endif // DEBUG	
	ios::sync_with_stdio(0); cin.tie(nullptr);
	int s = 0;
	cin >> s;
	auto vs = Read<pair<double, double>>();
#ifdef _DEBUG	
		printf("s=%d", s);
		//Out(que, "que=");
		Out(vs, "vs=");
		//Out(P, ",P=");
#endif // DEBUG	
		auto [a,b,c] = Solution().Ans(s,vs);
		if (c < 0)
		{
			printf("NO\n");
		}
		else
		{
			printf("%.2lf %.2lf %.0lf\n", a, b, c);
		}
	return 0;
};

扩展阅读

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

视频课程

先学简单的课程,请移步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 小时前
C++基于protobuf实现仿RabbitMQ消息队列---服务器模块认识1
服务器·开发语言·c++·分布式·rabbitmq·protobuf
晨非辰2 小时前
基于Win32 API控制台的贪吃蛇游戏:从设计到C语言实现详解
c语言·c++·人工智能·后端·python·深度学习·游戏
小此方2 小时前
Re: ゼロから学ぶ C++ 入門(六)类和对象·第三篇:运算符重载
开发语言·c++·后端
2301_789015622 小时前
每日精讲:环形链表、两个数组中的交集、随机链表的复制
c语言·数据结构·c++·算法·leetcode·链表·排序算法
2301_789015623 小时前
C++:二叉搜索树
c语言·开发语言·数据结构·c++·算法·排序算法
leiming611 小时前
C++ vector容器
开发语言·c++·算法
apocelipes13 小时前
从源码角度解析C++20新特性如何简化线程超时取消
c++·性能优化·golang·并发·c++20·linux编程
ozyzo13 小时前
求1~n的累加和
c++
charlie11451419114 小时前
现代C++嵌入式教程:C++98基础特性:从C到C++的演进(1)
c语言·开发语言·c++·笔记·学习·教程