题目描述 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;
}