BZOJ4699 : 树上的最短路

这道题主要是要解决以下两个问题:

问题1:

给定一个点$x$,如何取出所有经过它的下水道?

一条下水道经过$x$等价于它起点在$x$的子树里面且终点不在$x$的子树里面,或者两端点的lca就是$x$。

对于第一种情况,也就是说起点在$x$的dfs序子区间里,终点小于$st[x]$或者大于$en[x]$。

假设下水道是$A-B$向$C-D$连边,并且$st[A]<st[B]$,

那么只需要不断查询$st[A]$在$[L,R]$里$st[B]$最大的下水道,以及$st[B]$在$[L,R]$里$st[A]$最小的下水道即可。

用线段树按dfs序维护终点$dfn$最小和最大的下水道,然后不断取出即可。

注意到Dijkstra的时候,$dis$不降,所以对于一条下水道,只有$A-B$里第一个被访问到的点才会用到它,所以在使用完毕后直接删除即可。

问题2:

如何求最短路?

考虑转化问题,改成求起点到每条有向边的最短路,这个最短路包括这条边本身的权值。

按距离维护一个小根堆,里面有两种元素:

$1.$某条有向树边$x->y$

$2.$某条下水道$A-B,C-D$

每次取出堆顶元素,如果是树边,那么枚举$y$的出边,同时将经过$y$的下水道都取出即可。

如果是下水道,那么暴力将$C-D$路径上所有没有访问过的点的出边都取出。

因为每个点只需要取一次,所以用并查集完成路经压缩即可。

总时间复杂度$O((n+m)\log n)$,空间复杂度$O(n+m)$。

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef pair<ll,int>P;
const int N=250010,M=100010,T=262150,BUF=12000000,OUT=5000000;
char Buf[BUF],*buf=Buf,Out[OUT],*ou=Out;int Outn[30],Outcnt;
int n,m,S,i,j,C,D,x,y,z,g[N],v[N<<1],w[N<<1],nxt[N<<1],ed,fa[N],G[N],NXT[M];bool vis[M];
int size[N],f[N],d[N],son[N],top[N],dfn,st[N],en[N];
int l1[N],r1[N],l2[N],r2[N],ea[M],eb[M],va[T],vb[T],cur,q[99],pos[M],l;
ll ans[N];P t,h[N*2+M];
struct E{int u,v,z,a,b,c;}e[M];
void sorta(int l,int r){
  int i=l,j=r,m=e[ea[(l+r)>>1]].a;
  do{
    while(e[ea[i]].a<m)i++;
    while(e[ea[j]].a>m)j--;
    if(i<=j)swap(ea[i],ea[j]),i++,j--;
  }while(i<=j);
  if(l<j)sorta(l,j);
  if(i<r)sorta(i,r);
}
void sortb(int l,int r){
  int i=l,j=r,m=e[eb[(l+r)>>1]].b;
  do{
    while(e[eb[i]].b<m)i++;
    while(e[eb[j]].b>m)j--;
    if(i<=j)swap(eb[i],eb[j]),i++,j--;
  }while(i<=j);
  if(l<j)sortb(l,j);
  if(i<r)sortb(i,r);
}
inline void read(int&a){for(a=0;*buf<48;buf++);while(*buf>47)a=a*10+*buf++-48;}
inline void write(ll x){
  if(!x)*ou++=48;
  else{
    for(Outcnt=0;x;x/=10)Outn[++Outcnt]=x%10+48;
    while(Outcnt)*ou++=Outn[Outcnt--];
  }
}
inline void add(int x,int y,int z){v[++ed]=y;w[ed]=z;nxt[ed]=g[x];g[x]=ed;}
inline void ADD(int x,int y){NXT[y]=G[x];G[x]=y;}
void dfs(int x){
  size[x]=1;
  for(int i=g[x];i;i=nxt[i])if(v[i]!=f[x]){
    f[v[i]]=x,d[v[i]]=d[x]+1;
    dfs(v[i]),size[x]+=size[v[i]];
    if(size[v[i]]>size[son[x]])son[x]=v[i];
  }
}
void dfs2(int x,int y){
  st[x]=++dfn;top[x]=y;
  if(son[x])dfs2(son[x],y);
  for(int i=g[x];i;i=nxt[i])if(v[i]!=son[x]&&v[i]!=f[x])dfs2(v[i],v[i]);
  en[x]=dfn;
}
inline int lca(int x,int y){
  for(;top[x]!=top[y];x=f[top[x]])if(d[top[x]]<d[top[y]])swap(x,y);
  return d[x]<d[y]?x:y;
}
inline int mergea(int x,int y){
  if(!x||!y)return x+y;
  return e[ea[x]].b>e[ea[y]].b?x:y;
}
inline int mergeb(int x,int y){
  if(!x||!y)return x+y;
  return e[eb[x]].a<e[eb[y]].a?x:y;
}
void build(int x,int a,int b){
  if(a==b){
    pos[a]=x;
    va[x]=vb[x]=a;
    return;
  }
  int mid=(a+b)>>1;
  build(x<<1,a,mid),build(x<<1|1,mid+1,b);
  va[x]=mergea(va[x<<1],va[x<<1|1]);
  vb[x]=mergeb(vb[x<<1],vb[x<<1|1]);
}
void ask(int x,int a,int b){
  if(C<=a&&b<=D){q[cur++]=x;return;}
  int mid=(a+b)>>1;
  if(C<=mid)ask(x<<1,a,mid);
  if(D>mid)ask(x<<1|1,mid+1,b);
}
inline void dela(int x){
  va[x=pos[x]]=0;
  for(x>>=1;x;x>>=1)va[x]=mergea(va[x<<1],va[x<<1|1]);
}
inline void delb(int x){
  vb[x=pos[x]]=0;
  for(x>>=1;x;x>>=1)vb[x]=mergeb(vb[x<<1],vb[x<<1|1]);
}
inline void put(const P&x){
  h[++l]=x;
  for(int i=l;i>1&&h[i]<h[i>>1];i>>=1)swap(h[i],h[i>>1]);
}
inline void get(){
  t=h[1],h[1]=h[l--];
  for(int i=1;;){
    ll tmp=h[i].first;int j=0;
    if((i<<1)<=l&&h[i<<1].first<tmp)tmp=h[j=i<<1].first;
    if((i<<1|1)<=l&&h[i<<1|1].first<tmp)j=i<<1|1;
    if(j)swap(h[i],h[j]),i=j;else return;
  }
}
inline void up(int x,ll d){
  if(fa[x]!=x)return;
  fa[x]=f[x];
  ans[x]=d;
  for(i=g[x];i;i=nxt[i])put(P(d+w[i],-i));
  for(i=G[x];i;i=NXT[i])if(!vis[i])vis[i]=1,put(P(d+e[i].c,i));
  C=l1[st[x]],D=r1[en[x]];
  if(C<=D){
    cur=0;ask(1,1,m);
    while(1){
      for(y=i=0;i<cur;i++)y=mergea(y,va[q[i]]);
      if(!y)break;
      if(e[ea[y]].b<=en[x])break;
      dela(y);
      if(!vis[y=ea[y]])vis[y]=1,put(P(d+e[y].c,y));
    }
  }
  C=l2[st[x]],D=r2[en[x]];
  if(C<=D){
    cur=0;ask(1,1,m);
    while(1){
      for(y=i=0;i<cur;i++)y=mergeb(y,vb[q[i]]);
      if(!y)break;
      if(e[eb[y]].a>=st[x])break;
      delb(y);
      if(!vis[y=eb[y]])vis[y]=1,put(P(d+e[y].c,y));
    }
  }
}
int F(int x){return fa[x]==x?x:fa[x]=F(fa[x]);}
int main(){
  fread(Buf,1,BUF,stdin);read(n),read(m),read(S);
  for(i=1;i<n;i++)read(x),read(y),read(z),add(x,y,z),add(y,x,z);
  dfs(d[1]=1),dfs2(1,1);
  for(i=1;i<=m;i++){
    read(e[i].u),read(e[i].v),read(x),read(y),read(e[i].c);
    ADD(lca(x,y),i);
    e[i].z=d[lca(e[i].u,e[i].v)];
    x=st[x],y=st[y];
    if(x>y)swap(x,y);
    e[i].a=x,e[i].b=y;
    ea[i]=eb[i]=i;
  }
  if(!m)m=1;
  sorta(1,m);
  sortb(1,m);
  build(1,1,m);
  for(j=m+1,i=n;i;i--){
    while(j>1&&e[ea[j-1]].a>=i)j--;
    l1[i]=j;
  }
  for(j=0,i=1;i<=n;i++){
    while(j<m&&e[ea[j+1]].a<=i)j++;
    r1[i]=j;
  }
  for(j=m+1,i=n;i;i--){
    while(j>1&&e[eb[j-1]].b>=i)j--;
    l2[i]=j;
  }
  for(j=0,i=1;i<=n;i++){
    while(j<m&&e[eb[j+1]].b<=i)j++;
    r2[i]=j;
  }
  for(i=1;i<=n;i++)fa[i]=i;
  up(S,0);
  while(l){
    get();
    if(t.second>0){
      z=e[t.second].z;
      for(x=F(e[t.second].u);d[x]>=z;x=F(x))up(x,t.first);
      for(x=F(e[t.second].v);d[x]>=z;x=F(x))up(x,t.first);
    }else up(v[-t.second],t.first);
  }
  for(i=1;i<=n;i++)write(ans[i]),*ou++=‘\n‘;
  fwrite(Out,1,ou-Out,stdout);
  return 0;
}

  

时间: 2024-10-17 09:59:14

BZOJ4699 : 树上的最短路的相关文章

习题:树上的最短路(树链剖分优化建图)

题目 下水道的主干路由n个节点和\(n-1\)条边所组成,每条边通过它都需要一个时间\(t_i\),这种边是双向的 下水道上有一些塌陷,我们用\((l_1,r_1,l_2,r_2,c)\)来描述,表示从\(l_1\)到\(r_1\)路径上的点,到\(l_2\)和\(r_2\)路径上的任意一个点所需要的时间为\(c\),注意塌陷是单向的 求每一个点到目标节点\(k\)的最快时间 思路 到\(k\)转换\(k\)到每一个点,塌陷反过来建就好了 之后考虑塌陷 直接树链剖分暴力建图即可 代码 #incl

bzoj3047: Freda的传呼机 &amp;&amp; 2125: 最短路

Description 为了随时与rainbow快速交流,Freda制造了两部传呼机.Freda和rainbow所在的地方有N座房屋.M条双向光缆.每条光缆连接两座房屋,传呼机发出的信号只能沿着光缆传递,并且传呼机的信号从光缆的其中一端传递到另一端需要花费t单位时间.现在Freda要进行Q次试验,每次选取两座房屋,并想知道传呼机的信号在这两座房屋之间传递至少需要多长时间.Freda和rainbow简直弱爆了有木有T_T,请你帮帮他们吧……N座房屋通过光缆一定是连通的,并且这M条光缆有以下三类连接

【最近公共祖先Tarjan】Tarjan求LCA练习

Tarjan求LCA 这是一篇非常好的讲解,靠这个文章搞懂的~ 1 void tarjan(int u) 2 { 3 vis[u]=1; 4 for(int i=0;i<edge[u].size();i++) 5 { 6 int v=edge[u][i]; 7 if(vis[v] == 0) 8 { 9 tarjan(v); 10 p[v]=u; 11 } 12 } 13 for(int i=0;i<qy[u].size();i++) 14 { 15 int v=qy[u][i].v,id=q

图论——LCA、强联通分量、桥、割顶、二分图最大匹配、网络流

A: 交通运输线 时间限制: 5 Sec  内存限制: 128 MB 题目描述 战后有很多城市被严重破坏,我们需要重建城市.然而,有些建设材料只能在某些地方产生.因此,我们必须通过城市交通,来运送这些材料的城市.由于大部分道路已经在战争期间完全遭到破坏,可能有两个城市之间没有道路.当然在运输线中,更不可能存在圈. 现在,你的任务来了.给你战后的道路情况,我们想知道,两个城市之间是否存在道路,如果存在,输出这两个城市之间的最短路径长度. 输入 第一行一个整数Case(Case<=10)表示测试数据

HNOI2014 世界树

Description Input Output Sample Input 10 2 1 3 2 4 3 5 4 6 1 7 3 8 3 9 4 10 1 5 2 6 1 5 2 7 3 6 9 1 8 4 8 7 10 3 5 2 9 3 5 8 Sample Output 1 9 3 1 4 1 1 10 1 1 3 5 4 1 3 1 1 Data Constraint Hint 我们每次询问的关键点个数是有限的,实树上很多节点的情况实际是一样的,我们可以直接一起处理. 那么我们要构建一个

【CF700B】Connecting Universities(想法题,贪心,树上最短路)

题意:给出一棵树上的2*k个节点,给他们配对,使得他们之间的距离和最大. 思路:一条边的两侧如果有一侧没有给定的节点就不会被经过…… 如果有1个节点就会被经过1次…… 如果两侧分别有x,y个给定节点就会被经过min(x,y)次 因为要使总路程最大就是让每一条路被走过最多的次数 肯定是两侧各取一个 剩下的只能在某侧内部解决 所以按此统计即可 答案很大 用INT64 1 var head,vet,next,a,b,c,dep,flag,f:array[1..500000]of longint; 2

【刷题】【图论】树上最短路

给你n个叶子点互相的最短路长度,构造若干个点的最小生成树 这里引入的: 一个点到树的最短距离 #include<cstdio> #include<cstdlib> #include<algorithm> using namespace std; int n; const int N=33; int g[N][N];//注意我只存了,右上角 int main() { while(~scanf("%d",&n) && n) { f

bzoj3694: 最短路(树链剖分/并查集)

bzoj1576的帮我们跑好最短路版本23333(双倍经验!嘿嘿嘿 这题可以用树链剖分或并查集写.树链剖分非常显然,并查集的写法比较妙,涨了个姿势,原来并查集的路径压缩还能这么用... 首先对于不在最短路径树上的边x->y,设t为最短路径树上lca(x,y),则t到y上的路径上的点i到根的距离都可以用h[x]+dis[x][y]+h[y]-h[i](h[]为深度)来更新,因为h[i]一定,只要让h[x]+dis[x][y]+h[y]最小就行,这里用树剖直接修改整条链上的数,就可以过了. 并查集的

bzoj 1023: [SHOI2008]cactus仙人掌图 2125: 最短路 4728: 挪威的森林 静态仙人掌上路径长度的维护系列

%%% http://immortalco.blog.uoj.ac/blog/1955 一个通用的写法是建树,对每个环建一个新点,去掉环上的边,原先环上每个点到新点连边,边权为点到环根的最短/长路长度 1023 求仙人掌直径 树形dp,维护每个点向下的最长和次长路径长度,对原有的点直接更新答案,对新点可以把对应环上的点取出,倍长,破环成链,并用单调队列正反各扫一次 #include<cstdio> char buf[5000000],*ptr=buf-1; int _(){ int x=0,c