本文涉及知识点
计算几何
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++**实现。
