BZOJ1576 安全路径题解(最短路+并查集)

题目:BZOJ1576.
题目大意:给定一张 n n n个点 m m m条边的无向图,询问对于 1 1 1 n n n的每个点 i i i,最短的一条从 1 1 1 i i i的路径,使得这条路径的最后一条边与 1 1 1 i i i的最短路的最后一条边不同.保证对于任意 i i i 1 1 1 i i i的最短路径是唯一的.
1 ≤ n ≤ 1 0 5 , 1 ≤ m ≤ 2 ∗ 1 0 5 1\leq n\leq 10^5,1\leq m\leq 2*10^5 1n105,1m2105.

很容易想到我们可以先跑出一棵最短路树,这个可以直接用最短路算法实现.

然后对于每一条非树边 ( x , y , v ) (x,y,v) (x,y,v),考虑它可以对 ( x , y ) (x,y) (x,y)链上除LCA外的点 k k k,都提供一条路径 1 → x → y → k 1\rightarrow x\rightarrow y\rightarrow k 1xyk,容易发现其路径长度为 d i s [ x ] + d i s [ y ] + v − d i s [ k ] dis[x]+dis[y]+v-dis[k] dis[x]+dis[y]+vdis[k],其中 d i s [ i ] dis[i] dis[i]表示点 1 1 1到点 i i i的最短路径长度.

容易发现 d i s [ x ] + d i s [ y ] + v dis[x]+dis[y]+v dis[x]+dis[y]+v相对于一条确定的边 ( x , y , v ) (x,y,v) (x,y,v)来说是常数, − d i s [ k ] -dis[k] dis[k]相对于枚举的点 k k k也是常数,而我们要求每个点 k k k上的 d i s [ x ] + d i s [ y ] + v − d i s [ k ] dis[x]+dis[y]+v-dis[k] dis[x]+dis[y]+vdis[k]最小.

这个时候就可以把所有非树边按照 d i s [ x ] + d i s [ y ] + v dis[x]+dis[y]+v dis[x]+dis[y]+v从小到大排序,并依次枚举所有边,每次把链 ( x , y ) (x,y) (x,y)上除LCA外的所有点的答案覆盖为 d i s [ x ] + d i s [ y ] + v dis[x]+dis[y]+v dis[x]+dis[y]+v(减掉 d i s [ k ] dis[k] dis[k]可以在最后处理),并把这些点从树上删去,这可以用一个经典的并查集实现.

具体来说,我们定义并查集中一个点 x x x的祖先 a n c x anc_{x} ancx表示当前 x x x的祖先中第一个未被删去的点,然后可以用一种类似于树链剖分跳链的方式实现这个覆盖并删除的过程.

时间复杂度 O ( ( n + m ) log ⁡ m ) O((n+m)\log m) O((n+m)logm).

代码如下:

#include<bits/stdc++.h>
using namespace std;

typedef long long LL;

const int N=100000,M=200000,INF=(1<<30)-1;

int n,m;
struct side{
  int y,next,v;
}e[M*2+9];
int lin[N+9],cs;

void Ins(int x,int y,int v){e[++cs].y=y;e[cs].v=v;e[cs].next=lin[x];lin[x]=cs;}
void Ins2(int x,int y,int v){Ins(x,y,v);Ins(y,x,v);}

void into(){
  scanf("%d%d",&n,&m);
  for (int i=1;i<=m;++i){
    int x,y,v;
    scanf("%d%d%d",&x,&y,&v);
    Ins2(x,y,v);
  }
}

struct state{
  int x,v;
  state(int X=0,int V=0){x=X;v=V;}
  bool operator > (const state &p)const{return v>p.v;}
};
priority_queue<state,vector<state>,greater<state> >q;
int dis[N+9],vis[N+9];

void Dijkstra(){
  for (int i=1;i<=n;++i) dis[i]=INF,vis[i]=0;
  q.push(state(1,dis[1]=0));
  for (;!q.empty();){
    int t=q.top().x;q.pop();
    if (vis[t]) continue;
    vis[t]=1;
    for (int i=lin[t];i;i=e[i].next)
      if (dis[t]+e[i].v<dis[e[i].y])
        q.push(state(e[i].y,dis[e[i].y]=dis[t]+e[i].v));
  }
}

struct side2{
  int x,y,v,id;
  side2(int X=0,int Y=0,int V=0,int Id=0){x=X;y=Y;v=V;id=Id;}
}ord[M+9];
int co;

bool cmp(const side2 &a,const side2 &b){return a.v<b.v||a.v==b.v&&a.id<b.id;}

int fa[N+9],dep[N+9];

void Dfs_fa(int k,int fat){
  fa[k]=fat;
  dep[k]=dep[fat]+1;
  for (int i=lin[k];i;i=e[i].next){
    if (e[i].y==fat) continue;
    if (dis[k]+e[i].v==dis[e[i].y]) Dfs_fa(e[i].y,k);
    else if (k<e[i].y) ord[++co]=side2(k,e[i].y,dis[k]+dis[e[i].y]+e[i].v,co);
  }
}

struct union_find_set{
  int fa[N+9];
  int &operator [] (const int &p){return fa[p];}
  void Build(int l,int r){for (int i=l;i<=r;++i) fa[i]=i;}
  int Query_fa(int k){return k^fa[k]?fa[k]=Query_fa(fa[k]):k;}
}uni;

int ans[N+9];

void Get_ans(){
  uni.Build(1,n);
  sort(ord+1,ord+co+1,cmp);
  for (int i=1;i<=co;++i){
    int x=uni.Query_fa(ord[i].x),y=uni.Query_fa(ord[i].y),v=ord[i].v;
    for (;x^y;x=uni.Query_fa(x)){
      if (dep[x]<dep[y]) swap(x,y);
      ans[x]=v-dis[x];
      uni[x]=fa[x];
    }
  }
}

void work(){
  Dijkstra();
  Dfs_fa(1,0);
  Get_ans();
}

void outo(){
  for (int i=2;i<=n;++i)
    printf("%d\n",ans[i]?ans[i]:-1);
}

int main(){
  into();
  work();
  outo();
  return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值