道路与航线 —— 最短路 + 拓扑 + FloodFill

题面

传送门
在这里插入图片描述
在这里插入图片描述

思路

这道题SPFA会被卡,因此我们得考虑其他办法;


道路的特点:双向边,可能有环,边权非负

航线的特点:单向边,无环,边权可正可负

由此,我们认定只有道路的连通块是一个团;

也就是说在这个团内部,由于边权都是非负的,因此我们可以跑迪杰斯特拉;

而团与团之间是航线(有向边)连接的,如果我们将团看成一个整体,那么这就是一个DAG

如图所示;

在这里插入图片描述


由于是DAG,或者说是拓扑图,我们按照拓扑序的方式去更新一个个团,就可以得到最短路了;

采用拓扑序线性更新最短路,是可以处理负权边的(因为每个点只能由入它的点更新,而一旦入度为 0 0 0,那么最短路径就确定了);


在这里插入图片描述

注意

在调用迪杰斯特拉函数时候,不需要重置判重数组;即memset(vis,0,sizeof vis)

假设 d i j k s t r a dijkstra dijkstra正在某一个团 t 1 t1 t1里面运行,那么 v i s vis vis数组只会涉及将团 t 1 t1 t1里面的那些点更新成 t r u e true true,等到在下一个团 t 2 t2 t2里面跑 d i j k s t r a dijkstra dijkstra的时候, v i s vis vis需要用到的点是团 t 2 t2 t2里面的点,由于这些点只属于团 t 2 t2 t2,不属于之前的团,所以这些点一定没有被之前的团里面的 d i j k s t r a dijkstra dijkstra给更新成 t r u e true true,所以我们不需要重置判重数组;

调用 d i j k s t r a dijkstra dijkstra算法的时候,要将连通块内的所有点都压入优先队列内;

这里和我们往常确认起点的迪杰斯特拉不同;

因为确认起点则默认距离最小且第一次访问


对于一个团来说,我们现在无法确定哪些点最小,即不知道起点是谁,因此我们将团内所有点放入优先队列;

Code

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <queue>
#include <cstring>

using namespace std;

typedef long long ll;

typedef pair<int,int> pii;

const int N = 25000 + 10,M = 100010;

struct Edge{
    int next,to,val;
}e[M<<1];

vector<int> block[N];//每个连通块内存放的点

            //每个点属于的块号
int dist[N],id[N],block_cnt,head[N];

bool vis[N];

int n,road,sail,start,tot;

int in[N];//连通块的入度

void add(int u,int v,int w){
    e[++tot].to = v;
    e[tot].val = w;
    e[tot].next = head[u];
    head[u] = tot;
}

void dfs(int u,int blockId){
    id[u] = blockId;
    block[blockId].push_back(u);
    for(int i=head[u];i;i=e[i].next){
        int to = e[i].to;
        if(!id[to]) dfs(to,blockId);
    }
}
queue<int> topq;
void dij(int blockId){
    priority_queue<pii,vector<pii>,greater<pii>> pq;
    for(auto ver : block[blockId]){
        pq.push({dist[ver],ver});
    }
    while(!pq.empty()){
        int u = pq.top().second;
        pq.pop();
        if(vis[u]) continue;
        vis[u] = 1;
        for(int i=head[u];i;i=e[i].next){
            int to = e[i].to;
            if(dist[to] > dist[u] + e[i].val){
                dist[to] = dist[u] + e[i].val;
                //要求在同一连通块的内部才能入队迪杰斯特拉
                if(id[to] == id[u]) pq.push({dist[to],to});
            }
            //如果这条边是连通块之间的 那么需要减少入度
            if(id[to] != id[u]){
                //如果入度为0 那么需要入队
                if(--in[id[to]] == 0)
                    topq.push(id[to]);
            }
        }
    }
}
void topsort(){
    for(int i=1;i<=block_cnt;++i)
        if(!in[i]) topq.push(i);
    //连通块内迪杰斯特拉 连通块间线性更新
    while(!topq.empty()){
        int u = topq.front();
        topq.pop();
        dij(u);
    }
}
const int INF = 0x3f3f3f3f;
void solve(){
    cin >> n >> road >> sail >> start;
    memset(dist,0x3f,sizeof dist);
    dist[start] = 0;
    for(int i=1,u,v,w;i<=road;++i){
        cin >> u >> v >> w;
        add(u,v,w);
        add(v,u,w);
    }
    //DFS Flood Fill 扩展连通块
    for(int i=1;i<=n;++i)
        if(id[i] == 0)
            dfs(i,++block_cnt);
    for(int i=1,u,v,w;i<=sail;++i){
        cin >> u >> v >> w;
        add(u,v,w);
        ++in[id[v]];
    }
    topsort();
    for(int i=1;i<=n;++i){
        //因为负边权的存在 因此正无穷可能不是INF
        if(dist[i] > INF/2) cout << "NO PATH\n";
        else cout << dist[i] << '\n';
    }
}

int main(){
    std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
    solve();
    return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值