【洛谷3547】[POI2013] CEN-Price List(BFS)
- 有一张 \(n\) 个点 \(m\) 条边的无向图,边长均为 \(a\)。在原图中所有满足最短路长为 \(2a\) 的点对之间连一条边长为 \(b\) 的边。
- 求给定点 \(st\) 到所有点的距离。
- \(1\le n,m\le10^5\),\(1\le a,b\le10^3\)
原图上的 BFS
根据 \(b\) 与 \(a\) 的大小关系,最短路径可能有三种:全是 \(a\);若干 \(b\) 和一个 \(a\);全是 \(b\)。
先在原图中 BFS 一遍,求出 \(st\) 到所有点的最小边数 \(d_i\)。
也就是说,对于全是 \(a\) 的情况,最短路径长度就是 \(d_i\times a\)。
根据最短路的性质,最短路上第 \(k\) 个点和第 \(k+2\) 个点之间必然无边(否则就不是最短路了),因此我们必然可以把这条最短路径压缩,但可能会多出一个 \(a\)。
也就是说,对于若干个 \(b\) 和一个 \(a\) 的情况,最短路径长度就是 \(\lfloor\frac{d_i}2\rfloor\times b+(d_i\operatorname{mod}2)\times a\)。
只走 \(b\) 的BFS
然后考虑全是 \(b\) 的情况,我们重新 BFS 一遍,要求每次只能走 \(b\)。
有点类似于求三元环,但我们要求的实际上是三元非环。先枚举一遍当前点 \(x\) 连向的点 \(y\) 打标记,再枚举一遍当前点 \(x\) 连向的点 \(y\) 并枚举 \(y\) 连向的点 \(z\),如果 \(z\) 没被打上标记,那么就可以从 \(x\) 向 \(z\) 转移。
但我们并没有进行求三元环预先给边定向的操作,并且也无法进行这个操作,因此这样的复杂度是不正确的。
不过考虑 BFS 的性质,就是每个点 \(z\) 只会被转移一次。我们不妨在转移后就把这条边给删掉。
注意,具体实现中需要维护两个边集,其中枚举 \(x\) 连向的点是用原边集,枚举 \(y\) 连向的点是用转移边集,而我们删边仅是在转移边集中删去。
因此,第二重枚举中的一条边被枚举到时,除非它构成了一个三元环,否则就会被删去。而三元环的个数是 \(O(m\sqrt m)\) 的,故复杂度正确。
代码:\(O(m\sqrt m)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 100000
#define INF (int)1e9
using namespace std;
int n,m,st,a,b,d[N+5],q[N+5],vis[N+5],ans[N+5];
struct Graph
{
int ee,lnk[N+5];struct edge {int to,pre,nxt;}e[2*N+5];I Graph() {ee=1;}
I int operator [] (CI x) Cn {return lnk[x];}I edge operator () (CI x) Cn {return e[x];}
I void Add(CI x,CI y) {e[++ee].nxt=lnk[x],e[lnk[x]].pre=ee,e[lnk[x]=ee].to=y;}
I void Del(CI id) {(e[id].pre?e[e[id].pre].nxt:lnk[e[id^1].to])=e[id].nxt,e[e[id].nxt].pre=e[id].pre;}//删边
}G,K;
namespace FastIO
{
#define FS 100000
#define tc() (FA==FB&&(FB=(FA=FI)+fread(FI,1,FS,stdin),FA==FB)?EOF:*FA++)
#define pc(c) (FC==FE&&(clear(),0),*FC++=c)
int OT;char oc,FI[FS],FO[FS],OS[FS],*FA=FI,*FB=FI,*FC=FO,*FE=FO+FS;
I void clear() {fwrite(FO,1,FC-FO,stdout),FC=FO;}
Tp I void read(Ty& x) {x=0;W(!isdigit(oc=tc()));W(x=(x<<3)+(x<<1)+(oc&15),isdigit(oc=tc()));}
Ts I void read(Ty& x,Ar&... y) {read(x),read(y...);}
Tp I void writeln(Ty x) {W(OS[++OT]=x%10+48,x/=10);W(OT) pc(OS[OT--]);pc('\n');}
}using namespace FastIO;
int main()
{
RI i,j,x,y;for(read(n,m,st,a,b),i=1;i<=m;++i) read(x,y),G.Add(x,y),G.Add(y,x),K.Add(x,y),K.Add(y,x);
RI k,H,T;for(i=1;i<=n;++i) d[i]=-1;d[q[H=T=1]=st]=0;
W(H<=T) for(i=G[k=q[H++]];i;i=G(i).nxt) !~d[G(i).to]&&(d[q[++T]=G(i).to]=d[k]+1);//原图上的BFS
for(i=1;i<=n;++i) ans[i]=~d[i]?min(d[i]*a,(d[i]&1)*a+(d[i]>>1)*b):INF;
for(i=1;i<=n;++i) d[i]=-1;d[q[H=T=1]=st]=0;W(H<=T) {for(i=G[k=q[H++]];i;i=G(i).nxt) vis[G(i).to]=k;//给当前点连向的点打标记
for(i=G[k];i;i=G(i).nxt) for(j=K[G(i).to];j;j=K(j).nxt) vis[K(j).to]^k&&(K.Del(j),!~d[K(j).to]&&(d[q[++T]=K(j).to]=d[k]+1));}//二重枚举,不构成三元环即可删边
for(i=1;i<=n;++i) ~d[i]&&(ans[i]=min(ans[i],d[i]*b));for(i=1;i<=n;++i) writeln(ans[i]);return clear(),0;
}