[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;
}   

猜你喜欢

转载自www.cnblogs.com/Zfio/p/12809401.html