洛谷 P1951 收费站_NOI导刊2009提高(2) 最短路+二分

#[TOC]

#题面

##题目链接

P1951 收费站_NOI导刊2009提高(2)

其实还有一道双倍经验

P1462 通往奥格瑞玛的道路

##题目描述

在某个遥远的国家里,有n个城市。编号为1,2,3,…,n。

这个国家的政府修建了m条双向的公路。每条公路连接着两个城市。沿着某条公路,开车从一个城市到另一个城市,需要花费一定的汽油。

开车每经过一个城市,都会被收取一定的费用(包括起点和终点城市)。所有的收费站都在城市中,在城市间的公路上没有任何的收费站。

小红现在要开车从城市u到城市v(1 \(\leq u,v \leq n\) )。她的车最多可以装下 \(s\) 升的汽油。在出发的时候,车的油箱是满的,并且她在路上不想加油。

在路上,每经过一个城市,她都要交一定的费用。如果某次交的费用比较多,她的心情就会变得很糟。所以她想知道,在她能到达目的地的前提下,她交的费用中最多的一次最少是多少。这个问题对于她来说太难了,于是她找到了聪明的你,你能帮帮她吗?

##输入输出格式

###输入格式

第一行5个正整数, \(n,m,u,v,s\) ,分别表示有 \(n\) 个城市, \(m\) 条公路,从城市 \(u\) 到城市 \(v\) ,车的油箱的容量为 \(s\) 升。

接下来的有 \(n\) 行,每行1个整数, \(f_i\) 表示经过城市 \(i\) ,需要交费 \(f_i\) 元。

再接下来有 \(m\) 行,每行3个正整数,\(a_i,b_i,c_i (1 \leq a_i,b_i \leq n)\) ,表示城市 \(a_i\) 和城市 \(b_i\) 之间有一条公路,如果从城市 \(a_i\) 到城市 \(b_i\) ,或者从城市 \(b_i\) 到城市 \(a_i\) ,需要 \(c_i\) 升的汽油。

###输出格式

仅一个整数,表示小红交费最多的一次的最小值。

如果她无法到达城市 \(v\) ,输出-1

##输入输出样例

###输入样例:

4 4 2 3 8
8
5
6
10
2 1 2
2 4 1
1 3 4
3 4 3

###输出样例:

8

##说明

【数据规模】

对于60%的数据,满足 \(n \leq 200,m \leq 10000,s \leq 200\)

对于100%的数据,满足 \(n \leq 10000,m \leq 50000,s \leq 1000000000\)

对于100%的数据,满足 \(c_i \leq 1000000000,f_i \leq1000000000,\) 可能有两条边连接着相同的城市。

【时空限制】

1000ms,128M

#思路

首先看题目的问法,求最大值的最小,可以考虑到二分答案

首先假定当答案是x,那么他肯定要满足如果不经过交费超过x的点,跑一遍从u到v的最短路一定小于总油量

于是可以根据此多次跑最短路了

#AC代码


#include<bits/stdc++.h>
const int maxn=10010;
const int maxm=50010;
using namespace std;

int n,m,st,ed,W,wt[maxn];
int tot,to[maxm<<1],nxt[maxm<<1],head[maxn],len[maxm<<1];
int l,r,dis[maxn];
priority_queue< pair<int,int> > q;
bool vis[maxn],b;

void Dijkstra(int mx)
{
    for(int i=1;i<=n;i++) dis[i]=INT_MAX/2;
    for(int i=1;i<=n;i++) vis[i]=false;
    dis[st]=0;
    q.push(make_pair(0,st));
    while(!q.empty())
    {
        int u=q.top().second;
        q.pop();
        if(vis[u]) continue;
        vis[u]=true;
        for(int i=head[u];i;i=nxt[i])
        {
            int v=to[i];
            if(wt[v]>mx) continue;
            if(dis[u]+len[i]<dis[v])
            {
                dis[v]=dis[u]+len[i];
                q.push(make_pair(-dis[v],v));
            }
        }
    }
}

int main()
{
    scanf("%d%d%d%d%d",&n,&m,&st,&ed,&W);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&wt[i]);
        r=max(r,wt[i]);
    }
    for(int i=1;i<=m;i++)
    {
        int u,v,w;scanf("%d%d%d",&u,&v,&w);
        to[++tot]=v;nxt[tot]=head[u];len[tot]=w;head[u]=tot;
        to[++tot]=u;nxt[tot]=head[v];len[tot]=w;head[v]=tot;
    }
    l=max(wt[st],wt[ed]);
    do ///不要while! 否则如果wt[st]和wt[ed]相等就不能进循环了
    {
        int mid=(l+r)>>1;
        Dijkstra(mid);
        if(dis[ed]>W) l=mid+1;
        else r=mid,b=true;
    }while(l<r);
    if(b) printf("%d",r);
    else printf("-1");
    return 0;
}

#总结

这题第一遍做只得了80分,因为写的是while而不是do while,这个错很隐蔽。。如果l=0肯定就不会出这种情况了(吧)(所以我想说的是这样定左边界很灵性)

另外,判断是否可到终点的时候,放在r这一边也是一时想到(感谢Uranus巨巨)

posted @ 2018-10-03 20:03  Mercury04  阅读(269)  评论(1编辑  收藏  举报