一道巧用二分法的题目

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值