图和树(中)dijkstra--没有负边的单源点最短路径

Dijkstra

该算法主要用于解决途中没有负边的单源点最短路问题

复杂度 O((n+m)logn)

每次从可以到达的顶点中,选取路径最短的一条路径,并过呢更新其可到达的点距离源点的最短路径长度。
比较dis[y] > dis[x] + w的大小这一过程被称为松弛
若dis[y] > dis[x] + w,则松弛成功
更新dis[y]的大小
将y加入最小堆

每一条边只会被堆弹出一次
一旦某个点被最小堆谭舒,则不会被松弛,dis的值为最短路

#include<bis/stdc++.h>
#define N 200003
#define pa pair<int,int>
#define inf 1e9
using namespace std;
int dis[N],vis[N],n,m,s;
int tot,point[N],w[N],v[N],nxt[N];
void add(int x,int y,int z)
{
 tot++;
 nxt[tot] = point[x];
 point[x] = tot;
 w[tot] = z;
 v[tot] = y; 
} 
void dijkstra(int s )
{
  priority_queue<pa,vector<pa>,greater<pa>> q;
  //q中放的是已知路径长度的顶点
  //每次取出路径最短的点
  for(int i = 1;i<= n;i++)
   dis[i] == inf,vis[i] = 0;
   dis[s] = 0;
   q.push(make_pair(0,s));
   while(!q.empty())
   {
    int x = q.top.second();
    q.pop();
    if(vis[x]) continue;
    vis[x]= 1;//以为一个点可能被入堆多次,减少复杂度!
    for(int i = point[x];i;i=nxt[i])
     if(dis[v[i]] > dis[x] + w[i])
     {
     //松弛路径最短的点所邻接点的路径
      dis[v[i]] = dis[x] + w[i];
      q.push(mak_pair(dis[v[i]],v[i]));
     }
   }
}

应用问题

单向路径车站往返

有n个车站,其中1号车站为始发站,现有n-1个人,你需要安排他们分别去往除始发站以外的n-1个车站,然后返回始发站。交通系统的所有路径均为单向路径,连接两个不同的车站,每经过一条路径需要交纳一定的费用,你能求出总花费的最低金额嘛

输入格式

第一行一个整数T,表示测试用例的个数。
对于每个测试用例,输入格式如下
第一行两个整数n,m,分别表示车站的数量和车站之间的单向路径数。
接下来m行,每行三个数s,e,c,表示存在从s到e的单向路径,花费为c

输入样例

2
2 2
1 2 13
2 1 33
4 6
1 2 10
2 1 60
1 3 20
3 4 10
2 4 5
4 1 50

输出样例

46
210

问题分析:
从始发车站,跑单源点最短路径,可以的到始发点到任意车站的最短路。但是由于路径是单向的,从各个车站回来的时候,如果也从每一个车站跑一个单源点最短路,复杂度太大。如果用floyd O(n^3)也是难以接受的。
我们考虑,如果有一条最短路从车站a反回始发站0,那么将所有路径取反,从始发车站0,到车站a的最短路应该就是刚才那一条路径的反向。
我们就可以存两个图,同时存正向图和反向图。两次最短路就可以求到了。

#include<bits/stdc++.h>
#define N 1000010
#define pa pair<int,int>
#define inf 1e9
using namespace std;
int vis1[N],vis2[N],n,m;
int dis1[N],dis2[N];
int tot,point1[N],w1[N],v1[N],nxt1[N];
int point2[N],w2[N],v2[N],nxt2[N];
void init()
{
    tot = 0;
    memset(point1,0,sizeof(point1));
    memset(point2,0,sizeof(point2));
    memset(dis1,0,sizeof(dis1));
    memset(dis2,0,sizeof(dis2));
}

void add(int x,int y ,int z)
{
    tot++;
    nxt1[tot] = point1[x];
    point1[x] = tot;
    v1[tot] = y;
    w1[tot] = z;

    nxt2[tot] = point2[y];
    point2[y] = tot;
    v2[tot] = x;
    w2[tot] =  z;
}


void dijkstra(int n,int * point,int * dis,int * vis,int * w,int * v,int* nxt)
{
    int s = 1;
    priority_queue<pa, vector<pa>,greater<pa> > q;
    for(int i=1;i<=n;i++)
    {
        dis[i]=inf;
        vis[i]=0;
    } 
    dis[s]=0;
    q.push(make_pair(0,s));
    while(!q.empty())
    {
        int x = q.top().second;
        q.pop();
        if(vis[x]) continue;
        vis[x] =1;
        for(int i=point[x];i;i=nxt[i])
        if(dis[v[i]] > dis[x] + w[i])
        {
            dis[v[i]] = dis[x]+w[i];
            q.push(make_pair(dis[v[i]],v[i]));
        }
    }
}
 
int main()
{
   // freopen("a.in","r",stdin);
    //freopen("a.out","w",stdout);
    int T;
    cin>>T;
    for(int t=0;t <T;t++)
    {
        cin>>n>>m;
        init();
        for(int i =0 ;i < m;i++ )
        {
            int s,e,c;
            cin>>s>>e>>c;
            add(s,e,c);
        }
        dijkstra(n,point1,dis1,vis1,w1,v1,nxt1);
        dijkstra(n,point2,dis2,vis2,w2,v2,nxt2);
        int totalC = 0;
        for(int i=1;i<=n;i++)
        {
            totalC  = totalC + dis1[i] + dis2[i];
        }
        cout<<totalC <<endl;
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值