道路与航线

时间:2020-04-21
本文章向大家介绍道路与航线,主要包括道路与航线使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

链接

https://www.acwing.com/problem/content/description/344/

题目

农夫约翰正在一个新的销售区域对他的牛奶销售方案进行调查。

他想把牛奶送到T个城镇,编号为1~T。

这些城镇之间通过R条道路 (编号为1到R) 和P条航线 (编号为1到P) 连接。

每条道路 i 或者航线 i 连接城镇Ai到Bi,花费为Ci。

对于道路,0≤Ci≤10,000;然而航线的花费很神奇,花费Ci可能是负数(−10,000≤Ci≤10,000)。

道路是双向的,可以从Ai到Bi,也可以从Bi到Ai,花费都是Ci。

然而航线与之不同,只可以从Ai到Bi。

事实上,由于最近恐怖主义太嚣张,为了社会和谐,出台了一些政策:保证如果有一条航线可以从Ai到Bi,那么保证不可能通过一些道路和航线从Bi回到Ai。

由于约翰的奶牛世界公认十分给力,他需要运送奶牛到每一个城镇。

他想找到从发送中心城镇S把奶牛送到每个城镇的最便宜的方案。

输入格式
第一行包含四个整数T,R,P,S。

接下来R行,每行包含三个整数(表示一个道路)Ai,Bi,Ci。

接下来P行,每行包含三个整数(表示一条航线)Ai,Bi,Ci。

输出格式
第1..T行:第i行输出从S到达城镇i的最小花费,如果不存在,则输出“NO PATH”。

数据范围
\(1≤T≤25000, 1≤R,P≤50000, 1≤Ai,Bi,S≤T\)

输入样例:

6 3 3 4
1 2 5
3 4 5
5 6 10
3 5 -100
4 6 -100
1 3 -10

输出样例:

NO PATH
NO PATH
5
0
-95
-100

思路

题目背景很复杂,但是意思还是很简单的,一个包含单/双向边有负权边的图,并保证:如果有一条航线可以从Ai到Bi,那么保证不可能通过一些道路和航线从Bi回到Ai,求起点为S的单源最短路。有负权只能用SPFA,但这题会卡掉一般的SPFA算法。
两种优化:

1.SLF优化

使用双端队列,如果一个待加入节点的花费小于队头点的花费,则加入对头,否则加入队尾。 这种优化非常好写,但只是能玄学优化,其实还是很容易被卡掉的。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=25010,M=50000;
typedef pair<int,int> PII;
int h[N],cnt,inq[N],dis[N];
struct eg{
    int v,c,nex;
}e[M*3];
void addedge(int u,int v,int c){
    e[++cnt]=(eg){v,c,h[u]};
    h[u]=cnt;
}
void spfa(int st){
    deque<PII> q;
    memset(dis,0x3f,sizeof dis);
    dis[st]=0;inq[st]=1;
    q.push_back({st,0});
    while(!q.empty()){
        int u=q.front().first;q.pop_front();
        inq[u]=0;
        for(int i=h[u];~i;i=e[i].nex){
            int v=e[i].v,c=e[i].c;
            if(dis[v]>dis[u]+c){
                dis[v]=dis[u]+c;
                if(!inq[v]){
                    inq[v]=1;
                    if(q.empty()){
                        q.push_back({v,dis[v]});
                    }
                    else if(q.front().second>=dis[v]){
                        q.push_front({v,dis[v]});
                    }
                    else q.push_back({v,dis[v]});
                }
            }
        }
    }
}
int main(){
    memset(h,-1,sizeof h);
    int n,r,p,st;
    scanf("%d%d%d%d",&n,&r,&p,&st);
    for(int i=1;i<=r;++i){
        int u,v,c;
        scanf("%d%d%d",&u,&v,&c);
        addedge(u,v,c);
        addedge(v,u,c);
    }
    for(int i=1;i<=p;++i){
        int u,v,c;
        scanf("%d%d%d",&u,&v,&c);
        addedge(u,v,c);
    }
    spfa(st);
    for(int i=1;i<=n;++i){
        if(dis[i]==0x3f3f3f3f) printf("NO PATH\n");
        else printf("%d\n",dis[i]);
    }
    return 0;
}

2.拓扑序优化

题目给了一个重要的提醒,即:A通过单向边到B,不能通过其他路径从B到A。把双向边连接的点当作块,单向边连接的每个块,只看块和单向边的图具有拓扑序。

对于块内的点用迪杰斯特拉求最短路,块之间的点用拓扑序,每个块跑迪杰斯特拉之前保证被所有可到达的块更新过了。
wa在了两个操作上:
1.不可达点的距离d>INF/2即可,因为有负权边,正无穷也可以更新正无穷。
2.跑迪杰斯特拉时,只有在同一块的点才加入堆中,更新其他块的入度,如果块的入度等于0,加入块的编号到拓扑队列中。

代码

#include<bits/stdc++.h>
using namespace std;
const int N=25010,M=150010,INF=0x3f3f3f3f;
typedef pair<int,int> PII;
int h[N],e[M],w[M],nex[M],idx;
int d[N],st[N],q[N];
int id[N],ind[N],bcnt,hh,tt;
vector<int> blocks[N];
int n,mr,mp,S;
void add(int u,int v,int t){
    e[idx]=v;
    w[idx]=t;
    nex[idx]=h[u];
    h[u]=idx++;
}
void dij(int nid){
    priority_queue<PII> heap;
    for(int i=0;i<(int)blocks[nid].size();++i){
        heap.push({-d[blocks[nid][i]],blocks[nid][i]});
    }
    while(heap.size()){
        int u=heap.top().second;
        heap.pop();
        if(st[u]) continue;
        st[u]=1;
        for(int i=h[u];~i;i=nex[i]){
            int v=e[i];
            if(d[v]>d[u]+w[i]){
                d[v]=d[u]+w[i];
                if(id[v]==id[u]){
                    heap.push({-d[v],v});
                }
            }
            if(id[v]!=id[u]){
                ind[id[v]]--;
                if(ind[id[v]]==0){
                    q[tt++]=id[v];
                }
            }
        }
    }
}
void top_sort(){
    memset(d,INF,sizeof d);
    d[S]=0;
    for(int i=1;i<=bcnt;++i){
        if(!ind[i]) q[tt++]=i;
    }
    while(hh!=tt){
        dij(q[hh++]);
    }
}
void dfs(int u){
    id[u]=bcnt;
    blocks[bcnt].push_back(u);
    for(int i=h[u];~i;i=nex[i]){
        int v=e[i];
        if(!id[v])  dfs(v);
    }
}
int main(){
    memset(h,-1,sizeof h);
    cin>>n>>mr>>mp>>S;
    while(mr--){
        int u,v,w;
        cin>>u>>v>>w;
        add(u,v,w);
        add(v,u,w);
    }
    for(int i=1;i<=n;++i){
        if(!id[i]){
            bcnt++;
            dfs(i);
        }
    }
    while(mp--){
        int u,v,w;
        cin>>u>>v>>w;
        add(u,v,w);
        ind[id[v]]++;
    }
    top_sort();
    for(int i=1;i<=n;++i){
        if(d[i]>INF/2) cout<<"NO PATH\n";
        else cout<<d[i]<<endl;
    }
    return 0;
}

原文地址:https://www.cnblogs.com/jjl0229/p/12746193.html