codevs 4175 收费站(二分+spfa)

题目描述 Description
在某个遥远的国家里,有n个城市。编号为1,2,3,……,n。
这个国家的政府修建了m条双向的公路。每条公路连接着两个城市。沿着某条公路,开车从一个城市到另一个城市,需要花费一定的汽油。
开车每经过一个城市,都会被收取一定的费用(包括起点和终点城市)。所有的收费站都在城市中,在城市间的公路上没有任何的收费站。
小红现在要开车从城市u到城市v(1<=u,v<=n)。她的车最多可以装下s升的汽油。在出发的时候,车的油箱是满的,并且她在路上不想加油。
在路上,每经过一个城市,她要交一定的费用。如果她某次交的费用比较多,她的心情就会变得很糟。所以她想知道,在她能到达目的地的前提下,她交的费用中最多的一次最少是多少。这个问题对于她来说太难了,于是她找到了聪明的你,你能帮帮她吗?

输入描述 Input Description
第一行5个正整数,n,m,u,v,s。分别表示有n个城市,m条公路,从城市u到城市v,车的油箱的容量为s升。
接下来有n行,每行1个正整数,fi。表示经过城市i,需要交费fi元。
再接下来有m行,每行3个正整数,ai,bi,ci(1<=ai,bi<=n)。表示城市ai和城市bi之间有一条公路,如果从城市ai到城市bi,或者从城市bi到城市ai,需要用ci升汽油。

输出描述 Output Description
仅一个整数,表示小红交费最多的一次的最小值。
如果她无法到达城市v,输出-1。

样例输入 Sample Input
【输入样例1】
4 4 2 3 8
8
5
6
10
2 1 2
2 4 1
1 3 4
3 4 3
【输入样例2】
4 4 2 3 3
8
5
6
10
2 1 2
2 4 1
1 3 4
3 4 3

样例输出 Sample Output
【输出样例1】
8
【输出样例2】
-1

数据范围及提示 Data Size & Hint
对于60%的数据,满足n<=200,m<=10000,s<=200
对于100%的数据,满足n<=10000,m<=50000,s<=1000000000
对于100%的数据,满足ci<=1000000000,fi<=1000000000,可能有两条边连接着相同的城市。

题解:当看到要求最大值最小的时候我们就基本可以判断这是个二分了,这道题我们可以二分最多需要的费用,然后我们可以跑一遍spfa来验证,如果我们跑到的点的点权大于二分的费用,那么我们就不能走过去。最后如果到终点需要的油量大于油箱的容量,那么说明答案不合法,我们就要让最大费用变大。否则我们就可以让最大费用减小。二分的左界是起点和终点费用较大的一个减一,右界要取一个极大值。对于输出-1的情况,我们可以二分前跑一遍最短路,如果最短路需要的油量大于油箱容量,那么显然无法到达,输出-1。还有一件事,这题卡spfa,需要加slf优化,当然你也可以用dijkstra ╮(╯▽╰)╭。
PS:我刚开始左界没有减一,然后WA了,后来才知道存在一种情况是左界永远指向错误答案,右界永远指向正确答案,这样我们最后输出的答案就应该是右界,然而我们可能会错误的让右界在答案左边(%%%红太阳)。

代码如下

#include<queue> 
#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
const int MAXV=110000;
const int MAXE=510000;
int first[MAXV],nxt[MAXE<<1];
long long d[MAXV],pa[MAXV],s;//这道题需要用long long 
int n,m,ff,tt,tot;
bool used[MAXV];
struct edge
{
    int from,to;
    long long cost;
}es[MAXE<<1];
void init()//邻接表的初始化 
{
    memset(first,-1,sizeof(first));
    tot=0;
}
void build(int f,int t,long long d)
{
    es[++tot]=(edge){f,t,d};
    nxt[tot]=first[f];
    first[f]=tot;
}
deque<int> Q;
void init2()//每次spfa都要初始化 
{
    memset(d,63,sizeof(d));
    memset(used,0,sizeof(used));
    while(!Q.empty()) Q.pop_back();
}
bool spfa(int ss)//先跑一遍spfa看能不能跑到终点 
{
    init2();
    d[ss]=0;
    Q.push_front(ss);
    used[ss]=1;
    while(!Q.empty())
    {
        int u=Q.front();
        Q.pop_front();
        used[u]=0;
        for(int i=first[u];i!=-1;i=nxt[i])
        {
            int v=es[i].to;
            if(d[v]>d[u]+es[i].cost)
            {
                d[v]=d[u]+es[i].cost;
                if(!used[v])
                {
                    used[v]=1;
                    if(Q.empty()) Q.push_back(v);
                    else if(d[Q.front()]>d[v]) Q.push_front(v);
                    else Q.push_back(v);
                }
            }
        }
    }
    if(d[tt]>s) return false;//如果不能跑到终点 
    else return true;
}
bool check(int mid)//验证答案 
{
    init2();//每次初始化 
    d[ff]=0;
    Q.push_front(ff);
    used[ff]=1;
    while(!Q.empty())
    {
        int u=Q.front();
        Q.pop_front();
        used[u]=0;
        for(int i=first[u];i!=-1;i=nxt[i])
        {
            int v=es[i].to;
            if(pa[v]<=mid&&d[v]>d[u]+es[i].cost)//判断一下下一个点能不能走过去 
            {
                d[v]=d[u]+es[i].cost;
                if(!used[v])
                {
                    used[v]=1;
                    if(Q.empty()) Q.push_back(v);
                    else if(d[Q.front()]>d[v]) Q.push_front(v);
                    else Q.push_back(v);
                }
            }
        }
    }
    if(d[tt]>s) return false;//大于二分的答案,在左区间二分 
    else return true;//否则在右区间二分 
}
int main()
{
    scanf("%d%d%d%d%lld",&n,&m,&ff,&tt,&s);
    init();
    for(int i=1;i<=n;i++)
        scanf("%lld",&pa[i]);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        long long w;
        scanf("%d%d%lld",&u,&v,&w);
        build(u,v,w);
        build(v,u,w);
    }
    if(!spfa(ff))//不能到达终点直接输出-1 
    {
        printf("-1\n");
        return 0;
    }
    long long l=max(pa[ff],pa[tt])-1,r=1010000000,mid;//二分的左界需要注意一下 
    while(r-l>1)
    {
        mid=(l+r)/2;
        if(check(mid)) r=mid;
        else l=mid;
    }
    printf("%lld",l+1);//右界为答案 
    return 0;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值