BZOJ3694 最短路 [最短路径树]

11 篇文章 0 订阅
8 篇文章 0 订阅

3694: 最短路

Time Limit: 5 Sec   Memory Limit: 256 MB
Submit: 231   Solved: 119
[ Submit][ Status][ Discuss]

Description

给出一个n个点m条边的无向图,n个点的编号从1~n,定义源点为1。定义最短路树如下:从源点1经过边集T到任意一点i有且仅有一条路径,且这条路径是整个图1到i的最短路径,边集T构成最短路树。  给出最短路树,求对于除了源点1外的每个点i,求最短路,要求不经过给出的最短路树上的1到i的路径的最后一条边。

 

Input

第一行包含两个数n和m,表示图中有n个点和m条边。
接下来m行,每行有四个数ai,bi,li,ti,表示图中第i条边连接ai和bi权值为li,ti为1表示这条边是最短路树上的边,ti为0表示不是最短路树上的边。

Output

输出n-1个数,第i个数表示从1到i+1的要求的最短路。无法到达输出-1。

Sample Input

5 9
3 1 3 1
1 4 2 1
2 1 6 0
2 3 4 0
5 2 3 0
3 2 2 1
5 3 1 1
3 5 2 0
4 5 4 0

Sample Output

6 7 8 5

HINT

 对于100%的数据,n≤4000,m≤100000,1≤li≤100000

Source

思考

建出最短路径树,考虑对于每一条非树边 (u,v) ,则 u,v 及以上的点、 lca(u,v) 以下的点都会由这条非树边多出一条路径。求出 disi 1i 的最短路径,那么对于每个 u,v 及以上的点、 lca 以下的点,新增的这条路径的长度为 disu+disv+len(u,v)disx ,又因为对于每一个 x,disx 是不变的,现在我们就是求出能用来更新x的最小的非树边。
理解了图的性质就可以用并查集,令每一条非树边的权值为 disu+disv+len(u,v)disx ,排好序后一旦有一个点被更新了,就不会被其他的值更新,用并查集维护更新的链顶就行了。

#include<bits/stdc++.h>
using namespace std;
const int N=4e3+5,M=1e5+5,G=M<<1;
int n,m,tpt;
int tot,head[N];
int h[N],fa[N],dis[N],f[N],bel[N];
struct data{
    int x,y,l,len;
    inline bool operator < (const data &rhs)const{
        return len<rhs.len;
    }
}E[M];
struct point{
    int to,val,nxt;
}V[G];
template<class T>inline void read(T &res){
    static char ch;T flag=1;
    while((ch=getchar())<'0'||ch>'9')if(ch=='-')flag=-1;res=ch-48;
    while((ch=getchar())>='0'&&ch<='9')res=ch-48+res*10;res*=flag;
}
inline void addedge(int x,int y,int z){
    V[++tot].nxt=head[x],head[x]=tot,V[tot].to=y,V[tot].val=z;
}
inline void dfs(int x,int f,int dep){
    h[x]=dep,fa[x]=f;
    for(register int y,i=head[x];i;i=V[i].nxt)
        if(y=V[i].to,y!=f)
            dis[y]=dis[x]+V[i].val,dfs(y,x,dep+1);
}
inline int find(int x){
    return x==f[x]?x:f[x]=find(f[x]);
}
int main(){
    read(n),read(m);
    for(register int a,b,l,t,i=1;i<=m;++i){
        read(a),read(b),read(l),read(t);
        if(t==1)addedge(a,b,l),addedge(b,a,l);
        else E[++tpt].x=a,E[tpt].y=b,E[tpt].l=l;
    }
    dfs(1,0,1);
    for(register int i=1;i<=tpt;++i)E[i].len=E[i].l+dis[E[i].x]+dis[E[i].y];
    sort(E+1,E+tpt+1);
    for(register int i=1;i<=n;++i)f[i]=i;
    for(register int i=1;i<=tpt;++i){
        int u=E[i].x,v=E[i].y,ff=find(u),fs=find(v),tail=0,last=0;
        while(ff!=fs){
            if(h[ff]<h[fs])swap(u,v),swap(ff,fs),swap(tail,last);
            if(!bel[u]){
                bel[u]=i;
                if(tail)f[tail]=u;
            }else if(tail)f[tail]=ff;
            tail=ff,u=fa[tail],ff=find(u);
        }
    }
    if(bel[2])printf("%d",E[bel[2]].len-dis[2]);
    else printf("-1");
    for(register int i=3;i<=n;++i)
        if(bel[i])printf(" %d",E[bel[i]].len-dis[i]); 
        else printf(" -1");
    return 0;
}

这里写图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值