本文涉及的基础知识点
[ICPC2020 Shanghai R] Walker
题面翻译
作为著名的旅行者,Prof. Pang 的研究兴趣是在一生中到尽可能多的地方旅游。
在一条 [ 0 , n ] [0,n] [0,n] 的线段上有两名旅行者,第一名在位置 p 1 p_1 p1 以 v 1 v_1 v1 (每秒钟可以走 v 1 v_1 v1 单位的距离)的速度开始运动,第二名在位置 p 2 p_2 p2 以 v 2 v_2 v2 的速度开始运动。
从他们分别的起始点出发,旅行者可以在线段上移动,但不可以走出线段。他们可以随时改变自己的方向。
请帮助 Prof. Pang 计算至少需要多少时间,线段上的每个位置都至少被一名旅行者经过。
题目描述
As a world-famous traveler, Prof. Pang's research interest is to travel as many places as possible in his life.
We have a segment [ 0 , n ] [0, n] [0,n]. There are two travelers on it. The first one is on position p 1 p_1 p1 with velocity v 1 v_1 v1 (which means s/he can walk v 1 v_1 v1 unit on the segment per second). The second one is on position p 2 p_2 p2 with velocity v 2 v_2 v2.
From their respective beginning points, travelers can walk on the segment. They cannot walk outside the segment. Whenever they want to change their direction, they can turn around immediately.
Please help Prof. Pang to calculate the minimum possible time by which every position of the segment is passed by at least one traveler.
输入格式
The first line contains one integer t e s t ( 1 ≤ t e s t ≤ 10000 ) test~(1\le test\le 10000) test (1≤test≤10000) -- the number of test cases.
The i i i-th of the next t e s t test test lines contains five numbers n , p 1 , i , v 1 , i , p 2 , i , v 2 , i n, p_{1, i}, v_{1, i}, p_{2, i}, v_{2, i} n,p1,i,v1,i,p2,i,v2,i ( 0 < n ≤ 10000 0 < n \le 10000 0<n≤10000, 0 ≤ p 1 , i , p 2 , i ≤ n 0\le p_{1, i},p_{2, i} \le n 0≤p1,i,p2,i≤n, 0.001 ≤ v 1 , i , v 2 , i ≤ 1000 0.001 \le v_{1, i},v_{2, i} \le 1000 0.001≤v1,i,v2,i≤1000). All numbers have at most 3 3 3 digits after the decimal point.
输出格式
For each test case, we should output one number -- the minimum time that every position of the segment is passed by at least one traveler.
Your answer is considered correct if its absolute or relative error does not exceed 10 − 6 10^{-6} 10−6.
样例 #1
样例输入 #1
2
10000.0 1.0 0.001 9999.0 0.001
4306.063 4079.874 0.607 1033.423 0.847
样例输出 #1
5001000.0000000000
3827.8370013755
二分查找
性质一 :任意旅行者,走到最右的距离是r,最左的距离是left,则经过的范围是[left,r]。为使得r最大化,left最小化。不劣解:两种可能先左后右、先右后左。某解先到达left®,则不劣解: p → l e f t ( r ) → r ( l e f t ) p \rightarrow left(r) \rightarrow r(left) p→left(r)→r(left)。
性质二 :一个旅行者经过两个端点(0和n) ⟺ \iff ⟺ 经过全程。
情况一 :一个人走完全部旅途即一个人经过两个端点(0和n)。
情况二 :第一人经过0,第二人经过n。
情况三 :第一人经过n,第二人到达0。
二分
Cnt0(mid,p,v)如果无法到达0,则返回-1。如果能到达0,确保到达0,能到达的最大坐标。前提条件vmid >= p
先左后右,max(v mid-p,p) 先右后左:(vmid-p)/2+p
Cntn(mid,p,v)确保到达n的情况下,经过的最小坐标。前提条件 v mid >= n-p,否则返回n+1
先左后右:p-(vmid-(n-p))/2 先右后左 min(p,n-(v mid-(n-p)))
注意 :除异常外,Cnt0和Cntn返回值[0,n]。
二分类型:寻找首端。
参数范围:[0,1e9]
二分继续条件:r - left >= 1e-7。
Check函数:Cnt0 >= Cnt1
代码
误差要求1e-6,实际2.9e-6
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 <bitset>
using namespace std;
template<class T = int>
vector<T> Read(int n,const char* pFormat = "%d") {
vector<T> ret(n);
for(int i=0;i<n;i++) {
scanf(pFormat, &ret[i]);
}
return ret;
}
template<class T = int>
vector<T> Read( const char* pFormat = "%d") {
int n;
scanf("%d", &n);
vector<T> ret;
T d;
while (n--) {
scanf(pFormat, &d);
ret.emplace_back(d);
}
return ret;
}
string ReadChar(int n) {
string str;
char ch;
while (n--) {
do
{
scanf("%c", &ch);
} while (('\n' == ch));
str += ch;
}
return str;
}
template<class T1,class T2>
void ReadTo(pair<T1, T2>& pr) {
cin >> pr.first >> pr.second;
}
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:
double Ans(double n,double p1,double v1,double p2,double v2) {
auto Cnt0 = [&](double mid, double p, double v) {
const double remain = v * mid - p;
if (remain < 0) { return - 1.0; }
auto ans= max(remain, p+remain/2);
return min(n,ans);
};
auto Cntn = [&](double mid, double p, double v) {
const double remain = v * mid - (n - p);
if (remain < 0 ) { return n+1; }
auto ans = min(p - remain/2 , n- remain);
return max(0.0,ans);
};
auto Check = [&](double mid) {
if((Cnt0(mid,p1,v1)>=n)|| (Cnt0(mid, p2, v2) >= n)||
(Cntn(mid, p1, v1) <= 0) || (Cntn(mid, p2, v2) <= 0)) {
return true;
}
if (Cnt0(mid, p1, v1) >= Cntn(mid, p2, v2)) {
return true; }
return Cnt0(mid, p2, v2) >= Cntn(mid, p1, v1);
};
return CBinarySearch<double>(0, 1e9, 1e-7).FindFrist(Check);
}
const double m_tol = 1e-10;
};
int main() {
#ifdef _DEBUG
freopen("a.in", "r", stdin);
#endif // DEBUG
int T;
cin >> T;
while (T--) {
double n, p1, v1, p2, v2;
cin >> n >> p1 >> v1 >> p2 >> v2;
auto res = Solution().Ans(n,p1,v1,p2,v2);
cout << res << std::endl;
}
#ifdef _DEBUG
/*Out(mat, "mat=");
Out(L, "L=");
printf(",Q=%d;", Q);*/
#endif
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++**实现。