• 道路与航线


    链接

    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
    ");
            else printf("%d
    ",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
    ";
            else cout<<d[i]<<endl;
        }
        return 0;
    }
    
  • 相关阅读:
    Ubuntu mongodb 安装和配置
    最基本的SQL语法/语句
    Sphinx学习之sphinx的安装篇
    六关节机器人的雅可比矩阵及微分运算
    六关节机器人的逆运动学计算
    六关节机器人的正运动学计算
    六关节机器人末端的微分运动
    Python3 升级pip
    一般多项式曲线的最小二乘回归(Linear Regression)
    关于卡尔曼滤波(Kalman Filter)的很好讲解
  • 原文地址:https://www.cnblogs.com/jjl0229/p/12746193.html
Copyright © 2020-2023  润新知