[BZOJ2200][Usaco2011 Jan]道路和航线(拓扑排序+Dijkstra)

传送门


看上去就是要到最短路,但是这题数据经过构造会卡SPFA(大家都嫌弃他嘤嘤,but可以用SLF优化水过),且因为有负权会卡Dijkstra。

那么我们观察题意,发现只有单向边是有负权的,双向边没有负权,且单向边不会构成环!那么我们就可以把原图看作许多个由双向边组成的连通块,连通块由单向边互相连在一起形成一个DAG图,对于DAG图 我们就可以用拓扑序在线性时间内遍历求出答案,对于连通块内的最短路信息因为双向边没有负权可以用Dijkstra直接处理。

那么实现的话首先就可以先把双向边连起来,用dfs求出连通块,在拓扑排序遍历连通块的大框架下对于每一个连通块内的跑Dij即可。


#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
const int N=3e5+10,M=3e5+10;
const int INF=0x7f7f7f7f;
inline int read()
{
    int x=0,f=1; char ch=getchar();
    while(ch<'0' || ch>'9'){if(ch=='-')f=-1; ch=getchar();}
    while(ch>='0' && ch<='9'){x=x*10+ch-'0'; ch=getchar();}
    return x*f;
}
struct edge
{
    int x,y,c,next;
}a[N]; int len,last[N];
void ins(int x,int y,int c)
{
    a[++len].x=x;a[len].y=y;a[len].c=c;
    a[len].next=last[x];last[x]=len;
}

bool v[N];
int cnt;
vector<int> has[N];
int bl[N];
void dfs(int x)
{
    v[x]=1;
    has[cnt].push_back(x);
    for(int k=last[x];k;k=a[k].next)
    {
        int y=a[k].y;
        if(!v[y])bl[y]=bl[x],dfs(y);
    }
}

struct node
{
    int d,id;
    bool operator <(const node &b) const
    {
        return d>b.d;
    }
};

priority_queue<pair<int,int> > q;
int deg[N];
int n,R,P,st;
int list[M],head,tail;
int dis[N];
int main()
{
//	freopen("a.in","r",stdin);
//	freopen("a.out","w",stdout);
    n=read(); R=read(); P=read(); st=read();
    len=0; memset(last,0,sizeof(last));
    for(int i=1;i<=R;i++)
    {
        int x=read(),y=read(),c=read();
        ins(x,y,c);
        ins(y,x,c);
    }
//get scc
    cnt=0; memset(bl,0,sizeof(bl));
    memset(v,0,sizeof(v));
    for(int i=1;i<=n;i++)
        if(!v[i])
        {
            cnt++; bl[i]=cnt;
            dfs(i);
        }
//rebuild to DAG
    memset(deg,0,sizeof(deg));
    for(int i=1;i<=P;i++)
    {
        int x=read(),y=read(),c=read();
        ins(x,y,c); ins(bl[x]+n,bl[y]+n,c);
    }
//Get deg
    head=1,tail=1;
    list[1]=bl[st]+n;
    memset(v,0,sizeof(v));
    while(head<=tail)
    {
        int x=list[head];
        for(int k=last[x];k;k=a[k].next)
        {
            int y=a[k].y;
            if(!v[y])v[y]=1,list[++tail]=y;
            deg[y-n]++;
        }
        head++;
    }
//Dij+Topsort
    memset(dis,0x7f,sizeof(dis)); dis[st]=0;
    memset(v,0,sizeof(v));
    head=1,tail=1;
    list[1]=bl[st];
    while(head<=tail)
    {
        int cc=list[head];
//        for(int i=1;i<=n;i++)if(bl[i]==cc)q.push(make_pair(-dis[i],i));
        int siz=has[cc].size();
        for(int i=0;i<siz;i++) q.push(make_pair(-dis[has[cc][i]],has[cc][i]));
        while(q.size())
        {
            int x=q.top().second;q.pop();
            if(v[x])continue;
            v[x]=1;
            for(int k=last[x];k;k=a[k].next)
            {
                int y=a[k].y;
                if(bl[x]!=bl[y]){deg[bl[y]]--;
                        if(deg[bl[y]]==0)list[++tail]=bl[y];}
                if(dis[y]>dis[x]+a[k].c)
                {
                    dis[y]=dis[x]+a[k].c;
                    if(bl[x]==bl[y])q.push(make_pair(-dis[y],y));
                }
            }
        }
        
        head++;
    }

    for(int i=1;i<=n;i++)
    {
        if(dis[i]==INF) puts("NO PATH");
        else printf("%d\n",dis[i]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值