zoukankan      html  css  js  c++  java
  • [LOJ3255][JOI 2020 Final]奥运公交(最短路)

    [LOJ3255][JOI 2020 Final]奥运公交(最短路)

    题面

    给出一个(n)个点(m)条边的有向图,经过每条边需要费用(c_i).选择一条边并将其反向需要费用(d_i)(反向后经过的费用不变).问至多反向一条边,从1到n再回到1的最小花费

    (n leq 200,m leq 50000)

    分析

    考虑枚举反向的边(i=(u,v)),经过的费用为(w),反转的费用为(c).记(d(u,v))表示(u)(v)的最少费用,(f(i,u,v))表示不经过边(i)(u)(v)的最短路
    那么

    [d(1,n)=min(f(i,1,n),f(i,1,v)+w+f(i,u,n)) ]

    [d(n,1)=min(f(i,n,1)),f(i,n,v)+w+f(i,u,1)) ]

    这是因为从(1)(n)有2种选择:不经过((u,v))直接走到(n), 或者先到(v),经过((v,u))再到(n). 从(n)(1)的情况同理。总费用为(d(1,n)+d(n,1)+c)

    那么我们考虑如何求出(f).我们发现起点和终点只会是(1)(n).不妨考虑起点为1的情况。求出(operatorname{dist}(i))表示原图起点到(i)的费用,并求出一棵最短路树(T)

    [f(i,1,v)=egin{cases}operatorname{dist}(v),(u,v) otin T \ ext{原图上不经过}i ext{从原点到}v ext{的最小费用},(u,v)in Tend{cases} ]

    对于第一种情况直接开始时预处理即可。第二种情况需要重新跑一次最短路。注意到最短路树上只有(n-1)条边,第二种情况会出现(O(n))次。如果用堆优化的Dijkstra,单次复杂度为(O((n+m)log n)),因为(m)达到了(n^2)级别,总复杂度(O((n^2+nm)log n)=O(n^3log n)),,需要较强的常数优化才能通过。但是无堆优化的Dijkstra复杂度是(O(n^2+m))且常数很小,总复杂度(O(n^3))可以通过本题。

    其他的(f)同理可求:(f(i,u,n))(n)出发在原图的反图上跑最短路,(f(i,n,u))(n)出发在原图上跑最短路,(f(i,u,1))(1)出发在原图的反图上跑最短路. 代码可复用的部分较多,注意封装。

    代码

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    #define maxm 50000
    #define maxn 200
    #define INF 0x3f3f3f3f3f3f3f3f 
    using namespace std;
    typedef long long ll;
    int n,m;
    struct Graph{
        int S;
        struct edge{
            int from;
            int to;
            int len;
            int cost;
            int next;
        }E[maxm+5]; 
        int head[maxn+5];
        int esz;
        void add_edge(int u,int v,int w,int c){
            esz++;
            E[esz].from=u;
            E[esz].to=v;
            E[esz].len=w;
            E[esz].cost=c;
            E[esz].next=head[u];
            head[u]=esz;
        }
    
        ll dist1[maxn+5];
        int last[maxn+5];
        bool on_tree[maxm+5]; 
        void dijkstra(){//O(n^2)的Dijkstra,排除m的影响 
            static bool vis[maxn+5];
            memset(dist1,0x3f,sizeof(dist1));
            memset(vis,0,sizeof(vis));
            dist1[S]=0; 
            for(int p=1;p<=n;p++){
                int x=-1;
                for(int i=1;i<=n;i++) if(!vis[i]&&(x==-1||dist1[i]<dist1[x])) x=i;
                if(x==-1) break;
                vis[x]=1;
                for(int i=head[x];i;i=E[i].next){
                    int y=E[i].to;
                    if(dist1[y]>dist1[x]+E[i].len){
                        dist1[y]=dist1[x]+E[i].len;
                        last[y]=i;
                    }
                }
            }
            for(int i=1;i<=n;i++) if(i!=S) on_tree[last[i]]=1;
        }
    
        ll dist2[maxn+5];
        void dijkstra2(int bane){
            static bool vis[maxn+5];
            memset(dist2,0x3f,sizeof(dist2));
            memset(vis,0,sizeof(vis));
            dist2[S]=0;
            for(int p=1;p<=n;p++){
                int x=-1;
                for(int i=1;i<=n;i++) if(!vis[i]&&(x==-1||dist2[i]<dist2[x])) x=i;
                if(x==-1) break;
                vis[x]=1;
                for(int i=head[x];i;i=E[i].next){
                    int y=E[i].to;
                    if(i==bane) continue;//由于有重边,不能用端点来判断 
                    if(dist2[y]>dist2[x]+E[i].len) dist2[y]=dist2[x]+E[i].len;
                } 
            }
        }
        ll calc(int eid,int x){//不经过边i的1到x最短路 
            if(!on_tree[eid]) return dist1[x];
            else{//重新跑一遍最短路 
                dijkstra2(eid);
                return dist2[x];
            }
        }
    }G[4];
    
    
    int main(){
        static int u[maxm+5],v[maxm+5],w[maxm+5],c[maxm+5];
        scanf("%d %d",&n,&m);
        G[0].S=1;G[1].S=n;G[2].S=1;G[3].S=n;
        for(int i=1;i<=m;i++){
            scanf("%d %d %d %d",&u[i],&v[i],&w[i],&c[i]);
            G[0].add_edge(u[i],v[i],w[i],c[i]); 
            G[1].add_edge(v[i],u[i],w[i],c[i]);//反图上n的最短路树,对应正图上i到n的路径 
            G[2].add_edge(v[i],u[i],w[i],c[i]);
            G[3].add_edge(u[i],v[i],w[i],c[i]);
        }
        for(int i=0;i<4;i++) G[i].dijkstra();
        ll ans=G[0].dist1[n]+G[3].dist1[1];
        for(int i=1;i<=m;i++){
            ll d1=min(G[0].calc(i,n)/*1直接绕过i到n*/,G[0].calc(i,v[i])/*1->v*/+w[i]+G[1].calc(i,u[i])/*u->n*/);
            ll d2=min(G[3].calc(i,1)/*n直接绕过i到1*/,G[3].calc(i,v[i])/*n->v*/+w[i]+G[2].calc(i,u[i])/*u->1*/);
            if(d1>=INF||d2>=INF) continue;
            ans=min(ans,d1+d2+c[i]);
        } 
        if(ans>=INF) ans=-1;
        printf("%lld
    ",ans);
    }
    
  • 相关阅读:
    es5预览本地文件、es6练习代码演示案例
    Java实现 LeetCode 838 推多米诺(暴力模拟)
    Java实现 LeetCode 838 推多米诺(暴力模拟)
    Java实现 LeetCode 838 推多米诺(暴力模拟)
    Java实现 LeetCode 837 新21点(DP)
    Java实现 LeetCode 837 新21点(DP)
    Java实现 LeetCode 837 新21点(DP)
    Java实现 LeetCode 836 矩形重叠(暴力)
    Subversion under Linux [Reprint]
    Subversion how[Reprint]
  • 原文地址:https://www.cnblogs.com/birchtree/p/13412281.html
Copyright © 2011-2022 走看看