zoukankan      html  css  js  c++  java
  • [USACO11JAN] Roads and Planes

    解题报告

    题目链接:https://www.luogu.com.cn/problem/P3008

    分析

    上来一看就是最短路问题。

    首先,这题可能因为数据比较老的原因用SLF优化的SPFA可以过掉(但洛谷会卡到90分),我一开始就是这么水过的...SLF优化就是把普通SPFA的queue队列改成deque双端队列,在入队时跟目前的队首比较一下,如果比队首小就从前方入列,比队首大就从后方入列,利用的就是先扩展最小的点可以尽量减少我们之后松弛的操作次数,然鹅这个优化本身就跟SPFA一样有点玄学...他的时间效率也是不能保证的,只能说大部分时候是有点用的(笑)。

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #include<cstring>
    #include<deque>
    using namespace std;
    const int maxn=250000+1;
    #define Dio ios::sync_with_stdio(0)
    #define ll long long
    int m1,m2,n,t,S;
    int ans=0;
    int head[maxn],len=0;
    int dis[maxn];
    struct Edge{
        int to,next,dis;
    }edge[maxn];
    void Add(int u,int v,int w){
        edge[++len].next=head[u];
        edge[len].to=v;
        edge[len].dis=w;
        head[u]=len;
    }
    bool vis[maxn];
    //int cnt[maxn];
    deque<int> q;
    void Spfa(){
        for(int i=1;i<=n;i++)
            dis[i]=0x3f3f3f3f;
        dis[S]=0;
        vis[S]=1;
        q.push_back(S);
        while(!q.empty()){
            //cout<<1<<endl;
            int x=q.front();
            q.pop_front();
            vis[x]=0;
            //cnt[x]++;
            for(int i=head[x];i;i=edge[i].next){
                //cout<<1<<endl;
                int v=edge[i].to;
                //if(cnt[v]>n) continue ;
                if(dis[v]>dis[x]+edge[i].dis){
                    dis[v]=dis[x]+edge[i].dis;
                    if(!vis[v]){
                        vis[v]=1;
                        if(dis[v]<dis[q.front()]) q.push_front(v);
                        else q.push_back(v);
                    }
                }
            }
        }
    }
    int main(){
        Dio;
        cin>>n>>m1>>m2>>S;
        for(int i=1;i<=m1;i++){
            int u,v,w;
            cin>>u>>v>>w;
            Add(u,v,w),Add(v,u,w);
        }
        for(int i=1;i<=m2;i++){
            int u,v,w;
            cin>>u>>v>>w;
            Add(u,v,w);
        }
        Spfa();
        for(int i=1;i<=n;i++){
            if(dis[i]==0x3f3f3f3f)cout<<"NO PATH"<<endl;
            else cout<<dis[i]<<endl;
        }
        return 0;
    }
    

    然后是正解:

    对于整张图,边权有正有负,显然直接一波dij莽上去是不行的。但我们注意到,对于每一条道路,即双向边,边权都是正的,只有单向边可能带负权,而且保证不能让我们从后面的连通块跑回来。这就很友好了,一条条单向边相当于把整张图分成一个个的连通块(内部都是双向边),把每个连通块看作一个点,把单向边看作边,那么整张图是不是变成了一张DAG?在DAG上我们无论权正负都可以考虑按拓扑序进行扫描,求解最短路,套用到这道题上也是适用的。再一步就是处理每个连通块内部时,因为都是正权,直接Dij即可。

    然鹅即使这样在洛谷上依然有一个点会超时,我直接把蓝书上的代码粘过去也是一样...目前还在想解决的办法(难受)。

    然后问了问别人都是怎么过的,结果答案是吸氧...

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<deque>
    #include<queue>
    #define JoJo ios::sync_with_stdio(0)
    using namespace std;
    const int maxn=250000+1,maxm=1500000+1,inf=0x3f3f3f3f;
    int T,R,P,S;
    int head[maxn],len=0;
    struct Edge{
        int to,next,dis;
    }edge[maxn<<1];
    void Add(int u,int v,int w){
        edge[++len].to=v;
        edge[len].next=head[u];
        edge[len].dis=w;
        head[u]=len;
    }
    int scc_cnt=0;
    int belong[maxn],dis[maxn],deg[maxn];
    //belong[x]为x所属的连通块编号
    //deg[x]为第x个连通块的总入度
    void Dfs(int u){
        for(int i=head[u];i;i=edge[i].next){
            int v=edge[i].to;
            if(!belong[v]){
                belong[v]=scc_cnt;
                Dfs(v);
            }
        }
    }
    bool vis[maxn];
    queue<int> q;//普通队列用于保存连通块
    priority_queue<pair<int ,int> > Q;//优先队列用于跑Dij
    void Dij(){
        while(!q.empty()){
            int x=q.front();q.pop();
            for(int i=1;i<=T;i++)
                if(belong[i]==x) Q.push(make_pair(-dis[i],i));
            while(!Q.empty()){
                int t=Q.top().second;
                Q.pop();
                if(vis[t]) continue;
                vis[t]=1;
                for(int i=head[t];i;i=edge[i].next){
                    int v=edge[i].to;
                    if(dis[v]>dis[t]+edge[i].dis){
                        dis[v]=edge[i].dis+dis[t];
                        if(belong[t]==belong[v])Q.push(make_pair(-dis[v],v));//保证在同一个连通块内
                    }
                    if(belong[t]!=belong[v]&&!--deg[belong[v]]) q.push(belong[v]);//如果下一点不在该连通块内,把下一点所在的连通块入度--,对于每一个连通块,我们只需要找一个进入点
                }
            }
        }
    }
    int main(){
        JoJo;
        cin>>T>>R>>P>>S;
        memset(dis,0x7f,sizeof(dis));
        for(int i=1;i<=R;i++){
            int x,y,z;
            cin>>x>>y>>z;
            Add(x,y,z);
            Add(y,x,z);
        }
        for(int i=1;i<=T;i++){
            if(!belong[i]){
                belong[i]=++scc_cnt;
                Dfs(i);
            }
        }
        for(int i=1;i<=P;i++){
            int x,y,z;
            cin>>x>>y>>z;
            Add(x,y,z);    
            deg[belong[y]]++;//记录连通块入度
        }
        q.push(belong[S]);
        for(int i=1;i<=scc_cnt;i++)
            if(!deg[i]) q.push(i);
        dis[S]=0;
        Dij();
        for(int i=1;i<=T;i++){
            if(dis[i]>inf)cout<<"NO PATH"<<endl;
            else cout<<dis[i]<<endl;
        }
        return 0;
    }   
    
  • 相关阅读:
    NSCharacterSet 最经常使用的使用方法
    IOS
    hdu 3117 Fibonacci Numbers
    Hibernate5配置与使用具体解释
    SDNU 1206.蚂蚁感冒 【代码如此简单,思维练习】【7月29】
    2048游戏分析、讨论与扩展
    hash_set和hash_map
    实现邮箱找回的思路分析
    学习OpenCV——粒子滤波(网上两篇文章总结)
    学习OpenCV——配置CUDA环境
  • 原文地址:https://www.cnblogs.com/Zfio/p/12809401.html
Copyright © 2011-2022 走看看