1.
一条长度为L的公路,记为[0,L]。公路上上面随机分布着n个人,每个人初始向着0走或初始向着L走。给定这n个人的起始位置和起始方向。每个人每秒走1,求最少经过多长时间它们走过的路径能将这条公路覆盖。
样例输入
5 2
1 0
4 1
解释:5 2表示公路长度为5,上面有两个人。接下来的两行对应这两个人。1 0 表示第一个人位于公路上1这个位置,0表示他初始向着0方向走去。2 0表示第二个人位于公路上4这个位置,1表示他初始向着1这个方向走去。
样例输出:
3.5
思路:这道题一开始想着试图去找一些规律来做题,可是并没有什么规律可言。
看了群内大佬的大意,
二分法
。我利用二分法设计了一个思路。
设这条公路长为L。
1)首先先求出该问题解的一个最小上界。
求每个人初始到端点的距离。比如第一个人在1位置沿着0方向走去,那他到端点的距离就是1;再比如第二个人在4位置沿着L方向走去,那他到端点的距离就是(L-4),为每个人求一个这个初始距离,再求这些人的初始距离最小的那个,即为min_loc。
该问题解的最小上界就是min_loc+L。
float L; //公路长度
int people; //人数
scanf("%f%d",&L,&people);
for(int i=0;i<people;i++){
scanf("%f%d",loc+i,fangxiang+i);
}
int min_loc = INT_MAX;
for(int i=0;i<people;i++){
if(loc[i]<min_loc && fangxiang[i] == 0){
min_loc = loc[i];
}else if(fangxiang[i]==1 && L-loc[i]<min_loc){
min_loc = L-loc[i];
}
}
2)然后对(0,最小上界)这个时间区间不断进行二分。
每次二分求出的时间为mid,然后就判断初始经过mid这么长的时间是否能够把整个道路覆盖,如果
1)经过mid时间已经覆盖,把上界设为mid减去一个很小的值,继续二分。
2)经过mid时间仍未覆盖,把下届设为mid加上一个很小的值,继续二分。
(其实加不加很小的值无所谓)
float ans = min_loc + L;
float s = 0;
float mid;
while(s<=ans){
mid = (ans + s)/2.0;
if(check(mid,people,L)){ //已经覆盖
ans = mid - 1e-6;
}else{ //未覆盖
s = mid + 1e-6;
}
}
二分法讲解完了,那怎么判断经过mid时间路径能不能完全覆盖呢?
思路:我们将每个人经过mid时间覆盖的路径设为(start,end)。考虑到(start,end)要频繁使用,这里还是设计一个结构体比较好。
struct Edge{
float start; //路径的起始位置
float end; //路径的终止位置
Edge(float s,float e):start(s),end(e){};
bool operator< (Edge e) const{
if(start!=e.start){
return start>e.start; //start小的优先级高
}else{
return end>e.end; //start相同,end小的优先级高
}
}
};
求第i个结点在长度为L的公路上经过time时间覆盖的路径
Edge dist(int i,float L,float time){
float rest_dist = (fangxiang[i] == 0)?loc[i]:L-loc[i];
if(time>rest_dist){
if(fangxiang[i] == 0){
return Edge(0,(loc[i]>time-loc[i])?loc[i]:time-loc[i]);
}else{
return Edge((L-rest_dist<L-(time-rest_dist))?L-rest_dist:L-(time-rest_dist),L);
}
}else{
if(fangxiang[i] == 0){
return Edge(loc[i]-time,loc[i]);
}else{
return Edge(loc[i],loc[i]+time);
}
}
}
在判断经过mid时间这n个人能否覆盖整个路径的时候,先将这n个人经过mid时间走过的路径入队列。
bool check(float mid_time,int people,float L) {
//people-->人数
priority_queue<Edge> q;
while(!q.empty()){ //保证队列为空
q.pop();
}
for(int i=0;i<people;i++){
q.push(dist(i,L,mid_time)); //n个人经过mid时间走过的路径入队列
}
if(connect(q,L)){ //经connect判定覆盖了全部路径
return true;
}else{ //未覆盖全部路径
return false;
}
}
接下来,最后一步,编写connect函数用于判断优先队列里面的路径能否连起来,连起来是否是全部路径。这是两个判断,必须全满足,才能说覆盖了整条公路;否则,就没有覆盖整条公路。
在判断时,因为Edge重载了<运算符,所以能够保证优先队列中的后一项的start一定比前一项的start大。这样就能方便合并操作的进行。假设前一项是(a,b),后一项是(c,d),那么先进行判断c<=b,如果满足这个判断,则说明没断,就把他合并成(a,max(b,d)),然后将合并前的两项出队列,将新的Edge项入队列。当优先队列中只有一项时,就说明连成了一段,下面就可以进行判断,这一项的start是否为0,end是否为L,如果是,则覆盖了全部路径。否则,就没有。
bool connect(priority_queue<Edge> q,float L){
float s;
float e;
bool flag = true;
if(!q.empty()){
s = q.top().start;
e = q.top().end;
while(true){
if(q.size() == 1){
if(s == 0&&e == L){
flag = true; //两个条件都满足
}else{
flag = false; //能连起来,但连起来不是全部路径。
}
break;
}else{
q.pop(); //老项1出队列
if(q.top().start<=e){
e = (e>q.top().end)?e:q.top().end;
q.pop(); //老项2出队列
q.push(Edge(s,e)); //新的项入队列
}else{
flag = false; //断了,不能连起来。
break;
}
}
}
}else{
flag = false;
}
return flag; //返回结果
}
综上,此题基本上解决。但还没有测试过。
完整代码如下:
#include<iostream>
#include<string>
#include<queue>
using namespace std;
#define MAXN 20
float loc[MAXN];
int fangxiang[MAXN];
struct Edge{
float start; //路径的起始位置
float end; //路径的终止位置
Edge(float s,float e):start(s),end(e){};
bool operator< (Edge e) const{
if(start!=e.start){
return start>e.start; //start小的优先级高
}else{
return end>e.end; //start相同,end小的优先级高
}
}
};
Edge dist(int i,float L,float time){
float rest_dist = (fangxiang[i] == 0)?loc[i]:L-loc[i];
if(time>rest_dist){
if(fangxiang[i] == 0){
return Edge(0,(loc[i]>time-loc[i])?loc[i]:time-loc[i]);
}else{
return Edge((L-rest_dist<L-(time-rest_dist))?L-rest_dist:L-(time-rest_dist),L);
}
}else{
if(fangxiang[i] == 0){
return Edge(loc[i]-time,loc[i]);
}else{
return Edge(loc[i],loc[i]+time);
}
}
}
bool connect(priority_queue<Edge> q,float L){
float s;
float e;
bool flag = true;
if(!q.empty()){
s = q.top().start;
e = q.top().end;
while(true){
if(q.size() == 1){
if(s == 0&&e == L){
flag = true;
}else{
flag = false;
}
break;
}else{
q.pop();
if(q.top().start<=e){
e = (e>q.top().end)?e:q.top().end;
q.pop();
q.push(Edge(s,e));
}else{
flag = false;
break;
}
}
}
}else{
flag = false;
}
return flag;
}
bool check(float mid_time,int people,float L) {
//people-->人数
priority_queue<Edge> q;
while(!q.empty()){
q.pop();
}
for(int i=0;i<people;i++){
q.push(dist(i,L,mid_time));
}
if(connect(q,L)){
return true;
}else{
return false;
}
}
int main(){
float L; //公路长度
int people; //人数
scanf("%f%d",&L,&people);
for(int i=0;i<people;i++){
scanf("%f%d",loc+i,fangxiang+i);
}
int min_loc = INT_MAX;
for(int i=0;i<people;i++){
if(loc[i]<min_loc && fangxiang[i] == 0){
min_loc = loc[i];
}else if(fangxiang[i]==1 && L-loc[i]<min_loc){
min_loc = L-loc[i];
}
}
float ans = min_loc + L;
float s = 0;
float mid;
while(s<=ans){
mid = (ans + s)/2.0;
if(check(mid,people,L)){ //已经覆盖
ans = mid - 1e-6;
}else{ //未覆盖
s = mid + 1e-6;
}
}
cout<<mid<<endl;
return 0;
}