您的位置:首页 > 编程语言 > C语言/C++

Cpp环境【Code[VS]4175】【CQYZOJ1824】收费站

2016-09-10 17:43 429 查看
【问题描述】


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

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

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

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

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

【输入格式】


  第一行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升汽油。

【输出格式】


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

【输入样例】


【样例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

【输出样例】


【样例1】

8

【样例2】

-1

【数据范围】


对于60%的数据,满足n<=200,m<=10000,s<=200

对于100%的数据,满足n<=10000,m<=50000,s<=1000000000

对于100%的数据,满足ci<=1000000000,fi<=1000000000,可能有两条边连接着相同的城市。

【原题传送矩阵】


Code[VS]4175 传送矩阵

CQYZOJ 1827 传送矩阵

【思路梳理】


不算难的图论题,二分猜答案+Dijstra算法就可以通过,类似于笔者写过的另一道题目盛夏的果实,区别在于本题限制更多,难度更大。

考虑到本题的边数目较多,所以SPFA是只能通过70%数据的,Floyd更不要考虑(使用Kruskal优化后也只能通过80%的数据),原因很简单,SPFA是BFS的升级版,每一个点都有可能会多次进入队列(如果不存在负权回路的话,最大情况每个点都可能会进入队列n-1次),SPFA时间复杂度不稳定且反复计算dist浪费了大量的时间;而根据题目的描述显然不可能出现负权路,那么我们可以放心使用Dijstra。

大致思路如下:先猜测最大值最小的点权值,那么在进行Dijstra时,凡是点权值大于猜测值的点都应该被我们用条件掐掉,检视是否能在目前的情况下建立起一条从u到v的最小路,使得dist[v]<=s:如果不能说明被掐掉的点太多,我们应该在右半个区间进行猜测;否则说明该答案可行,检视能否让该点的值更小、掐掉更多的点。

注意细节,提升算法水平!细心点,可以一次通过的!

【Cpp代码】


#include<cstdio>
#include<queue>
#include<vector>
#include<cstring>
#include<queue>
#include<iostream>
#include<algorithm>
#define maxn 10005
#define inf 1000000005
using namespace std;
int n,m,start,end,s,fare[maxn],ans=inf;
bool ok=false;
vector<int>g[maxn],w[maxn];
int d[maxn];

struct data
{
int id,dist;
friend bool operator<(data a,data b)
{
return a.dist>b.dist;
}
};

bool Dijstra(int mid)//朴素的Dijstra,不赘述细节
{
priority_queue<data>q;
for(int i=1;i<=n;i++)   d[i]=inf;
d[start]=0;
q.push((data){start,0});

while(!q.empty())
{
int i=q.top().id,dist=q.top().dist;
q.pop();
if(fare[i]>mid)     continue;
if(d[i]>dist || dist>s) continue;//注意第二个条件:如果到达i点所需要的油量已经大于了油箱容量、不能达到i点,显然更不可能到达v点(一定有d[v]>d[i])
d[i]=dist;
for(int j=0;j<g[i].size();j++)
{
int k=g[i][j],c=w[i][j];
if(fare[k]>mid)     continue;//假删除一些点
if(d[k]>dist+c)//松弛操作
{
d[k]=dist+c;
q.push((data){k,d[k]});
}
}
if(d[end]<=s)//不一定需要等到所有的边都考虑完再判断是否能到达终点,降低实际耗时
{
ok=true;//ok:如果至少有一次到达了终点,那么ok=1
return true;
}
}
return false;
}

void solve()//二分猜答案,猜测最大值最小的点权
{
int left=0,right=inf;
while(left<=right)
{
int mid=(left+right)/2;
if(Dijstra(mid))    right=mid-1;
else left=mid+1;
}
if(ok)  cout<<left;//至少有一种方案能到达终点
else cout<<"-1";//一次都不可能到达终点
}

int main()
{
scanf("%d%d%d%d%d",&n,&m,&start,&end,&s);
for(int i=1;i<=n;i++)   scanf("%d",&fare[i]);
for(int i=1;i<=m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
g[a].push_back(b);
g[b].push_back(a);
w[a].push_back(c);
w[b].push_back(c);
}

if(start!=end)  solve();//如果起点终点一样直接输出该点点权就可
else cout<<fare[start];
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息