Luogu P6397 [COI2008] GLASNICI 解题报告
一条直线上有
n 个信使,将他们按照从左至右的顺序以1 至n 编号。换句话说,设i 号信使的的坐标为d_i ,则对于1 \leq i \lt n ,d_i \leq d_{i + 1} 。信使传递一条消息的方法如下:
- 在任意时刻(不一定是整数时刻),任一信使(无论是否已知消息)都可以自由选择向左移动或者向右移动或者原地不动。其移动的速度为每秒
1 单位长度。- 当两个信使相距不超过一给定实数
k 时,双方可以进行消息传递,也即如果两人中有一人已知该消息,则两人都知道了该消息。消息传递是瞬间发生的,不消耗时间。现在
1 号信使得到了一条消息,请求出最小的让所有信使都得到该消息的用时。
1 \leq n \leq 10^5,0 \leq k \leq 10^6
解题思路
很巧妙的思维题。
首先把问题转化一下,对于一个点
可以画个图加以理解。
我们发现,这个用时肯定满足单调性,所以我们考虑二分。接着考虑边界,左边界肯定为
关于实数域上的二分,我们可以这么写.
const double eps = 1e-6;//输出k位小数,eps最好设为10^(-k-2)
double l = 0.0,r = pos[n] - k - pos[1];
while (l + eps < r) {
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
接着考虑 check
怎么写。我们可以想一下两个点是如何相遇的:当前点
结合图理解更佳。
所以我们可以弄一个指针
now + k \geq x_{i+1} 这种情况就贪心的让now = now + k now + k < x_{i+1} 这种情况我们没有办法,只能让now = x_i + k now < x_i - k - mid 这样无论怎么都到达不了了,mid 就肯定不能取到。
变成代码就是
bool check(double x) {
double now = pos[1] + x;//初始化
for (int i = 2;i <= n;++i) {
if (pos[i] - k - now > x) return 0;//情况3
now = min(now + k,pos[i] + x);//情况1和情况2
}
return 1;
}
所以我们就在
代码
#include <cstring>
#include <cstdio>
#include <cctype>
#include <cmath>
#include <algorithm>
using namespace std;
#define ll long long
#define ri register int
const int N = 100010;
const double eps = 1e-6;
double pos[N],k;
int n;
bool check(double x) {
double now = pos[1] + x;
for (int i = 2;i <= n;++i) {
if (pos[i] - k - now > x) return 0;
now = min(now + k,pos[i] + x);
}
return 1;
}
signed main() {
scanf("%lf\n%d",&k,&n);
for (int i = 1;i <= n;++i) scanf("%lf",&pos[i]);
double l = 0.0,r = pos[n] - k - pos[1];
while (l + eps < r) {
double mid = (l + r) / 2;
if (check(mid)) r = mid;
else l = mid;
}
printf("%.4f",l);
return 0;
}
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
评论已关闭