把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷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;
}
posted @ 2021-10-28 07:51  TheLostWeak  阅读(48)  评论(0编辑  收藏  举报