ZOJ 3946 Highway Project(最小费用最短路,优先队列优化dijkstra)

本文针对一个具体的图论问题——高速公路项目,介绍了如何找到从首都到其他城市的总旅行时间最小的建设方案,在时间相同时选择成本最低的方案。通过优化Dijkstra算法并结合优先队列实现高效求解。

ZOJ 3946 Highway Project

Edward, the emperor of the Marjar Empire, wants to build some bidirectional highways so that he can reach other cities from the capital as fast as possible. Thus, he proposed the highway project.
The Marjar Empire has N cities (including the capital), indexed from 0 to N - 1 (the capital is 0) and there are M highways can be built. Building the i-th highway costs Ci dollars. It takes Di minutes to travel between city Xi and Yi on the i-th highway.
Edward wants to find a construction plan with minimal total time needed to reach other cities from the capital, i.e. the sum of minimal time needed to travel from the capital to city i (1 ≤ i ≤ N). Among all feasible plans, Edward wants to select the plan with minimal cost. Please help him to finish this task.
**Input**
There are multiple test cases. The first line of input contains an integer T indicating the number of test cases. For each test case:
The first contains two integers N, M (1 ≤ N, M ≤ 105).
Then followed by M lines, each line contains four integers Xi, Yi, Di, Ci (0 ≤ Xi, Yi < N, 0 < Di, Ci < 105).
**Output**
For each test case, output two integers indicating the minimal total time and the minimal cost for the highway project when the total time is minimized.
**Sample Input**
2
4 5
0 3 1 1
0 1 1 1
0 2 10 10
2 1 1 1
2 3 1 2
4 5
0 3 1 1
0 1 1 1
0 2 10 10
2 1 2 1
2 3 1 2
**Sample Output**
4 3
4 4
题意:有N点,M边,已知每条边修建所需费用和走过所需时间,求使从0出发分别向其他点走的总时间最小的道路修建方案,在总时间相同的情况下,选择所需总费用最小的方案。输出总时间和总费用。
乍看像是最小生成树,仔细一看才发现是单源最短路问题,附加了一个费用最小的判断。
直接用dijkstra会超时,需要用优先队列优化一下;N比较大,需要用邻接表存储边。
关于锁定最短最小费用边,我是这样操作的:
记录每个点被访问时所经过的最后一条边,并在松弛操作中,对于每条不改变改点最短距离的边,将其费用与先前存储的边的费用比较,取费用较小的一条边作为新的最后访问边。
这段操作代码如下:

for(k=head[n];k!=-1;k=edge[k].next){
      m=edge[k].to;
      if(!vis[m]&&d[m]>d[n]+edge[k].t){
          d[m]=d[n]+edge[k].t;
          pre[m]=k;
          in.num=m;
          in.dis=d[m];
          Q.push(in);
          continue;
      }
      if(d[m]==d[n]+edge[k].t){
          if(edge[k].w<edge[pre[m]].w){
               pre[m]=k;
          }
      }
}

这道题还有一些需要注意的地方:

1)数据范围,对最短距离的存储,数据最大可达1e10,需要用long long

2)初始化最短距离数组的INF,需要define为1e10

在这道题wa了一下午,直接导致了我们队模拟训练成功打铁,最后发现是输出的%I64d出了问题…改成cout就一发入魂了

这个故事告诉我们,输出量小的时候,就不要纠结什么%lld,什么%I64d了,用cout还是很好的…

对于编译一直有很多不解的地方,比如为什么有的题用G++会TLE,会WA,用C++就能A,有的题则是G++能A,还有什么时候用%I64d,什么时候用%lld,都不太了解,是时候补习一下了。

以及对于优先队列的使用还不是很熟练,不会设置先出小的优先队列。补习。

代码如下:

#include<cstdio>
#include<cstring>
#define INF 1e10+5
#include<iostream>
#include<queue>
#include<algorithm>
#define maxn 100005
using namespace std;
int top;
int N,M;
typedef struct{
    int to,w,next,t;
}edgenode;
edgenode edge[maxn*2];
typedef struct{
    int num,dis;
}node;
int head[maxn];
bool vis[maxn];
bool road[maxn];
long long d[maxn];
int pre[maxn];
bool operator<(node a,node b){
    if(a.dis==b.dis)
        return a.num>b.num;
    return a.dis>b.dis;//先出小
}
int add(int s,int e,int t,int w){
    edge[top].to=e;
    edge[top].w=w;
    edge[top].next=head[s];
    edge[top].t=t;
    head[s]=top++;
    return 0;
}
int main(){
    int i,k,T;
    int s,e,w,t,n,m;
    long long sumt,sumc;
    node in,out;
    scanf("%d",&T);
    while(T--){
        top=1;
        sumc=0;
        sumt=0;
        scanf("%d %d",&N,&M);
        memset(head,-1,sizeof(head));
        memset(road,false,sizeof(road));
        memset(pre,-1,sizeof(pre));
        memset(vis,false,sizeof(vis));
        for(i=1;i<=M;i++){
            scanf("%d %d %d %d",&s,&e,&t,&w);
            add(s,e,t,w);
            add(e,s,t,w);
        }
        for(i=0;i<N;i++){
            d[i]=INF;
        }//初始化
        d[0]=0;
        in.num=0;
        in.dis=0;
        priority_queue<node> Q;
        Q.push(in);
        while(!Q.empty()){
            out=Q.top();
            Q.pop();
            n=out.num;
            vis[n]=true;
            for(k=head[n];k!=-1;k=edge[k].next){
                m=edge[k].to;
                if(!vis[m]&&d[m]>d[n]+edge[k].t){
                    d[m]=d[n]+edge[k].t;
                    pre[m]=k;
                    in.num=m;
                    in.dis=d[m];
                    Q.push(in);
                    continue;
                }
                if(d[m]==d[n]+edge[k].t){
                    if(edge[k].w<edge[pre[m]].w){
                        pre[m]=k;
                    }
                }
            }
        }
        for(i=0;i<N;i++){
            sumt+=d[i];
        }
        cout<<sumt;
        cout<<' ';
        for(i=1;i<N;i++){
            sumc+=edge[pre[i]].w;
        }
        cout<<sumc<<endl;
    }
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值