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

【洛谷5236】【模板】静态仙人掌(圆方树板子)

点此看题面

大致题意: 给定一个仙人掌,询问若干对点的最短路。

前言

今天似乎真的是运气爆棚了。

这道题自去年省选\(Day1\)没调出来,已经咕了\(14\)个月了,结果今天居然一遍过了。

说到这里,我还因此去水自己以前的游记水了半个多小时。。。

关于静(hl)态(666)仙(的)人(左)掌(手)

这部分为对hl666的膜拜,可自行跳过。

众所周知,静态仙人掌就是仙人\(hl666\)用来切题的左手,而动态仙人掌则是仙人\(hl666\)用来吊锤集训队的右手。

什么?你说为什么切题是静态的?

那当然是因为\(hl666\)早已切完了所有的题,已经无题可切了,所以他切题的左手也就只能不甘地保持静态了。

不过,每当到了月圆之夜\(ZJOI\)\(NOI\)\(hl666\)的左手就会化静为动,再次进入动态切题模式。

好了,对hl666的膜拜暂且告一段落,接下来开始正式讲这道题目。

圆方树

什么是圆方树?

简单的说,就是由一堆圆点和一堆方点组成的树。废话。

实际上,若要解释什么事圆方树,首先,我们需要知道两个定义:

  1. 仙人掌:任意一条边至多只出现在一条简单回路的无向连通图称为仙人掌。
  2. 点双连通分量:在无向连通图中,如果删除该图的任何一个点都不能改变该图的连通性,则该图为点双连通分量,简称点双。

有一个比较显然的性质,根据仙人掌的定义,仙人掌中的点双实际上就是一个环。

圆方树,其实就是把仙人掌上原有的点看作圆点,而对于每一个点双新建一个方点,然后将每一个圆点向它所在的点双所对应的方点连边(注意,根据点双的定义,一个点可能位于多个点双),这样就形成了一棵树。(具体建图类似于\(Tarjan\),可详见代码,代码中有解释)

显然,与一张没有规律的图相比,同样的问题放到树上可能就会简单许多,举个例子,就好比这道题的最短路问题。

因此,圆方树被称为解决仙人掌问题的利器。

关于此题

然后,让我们考虑怎么做这道题。

我们令圆方树上圆点与圆点间边权为原仙人掌的边权,圆点与方点间边权为点双中该圆点此方点父节点(必然是圆点)的最短路。

对于一个询问\(x,y\),我们首先求出它们的\(LCA\),令其为\(f\)

\(f\)是圆点,则\(x\)\(y\)的最短路就是圆方树中的树上路径。

\(f\)是方点,则我们设从\(x\)\(y\)向上跳到点双所对应的点分别为\(a,b\)(可以在倍增\(LCA\)中一并求出),则最终答案就是\(x\)\(a\)\(a\)\(b\)\(b\)\(y\)的距离。

\(x\)\(a\)\(b\)\(y\)的距离都可以直接求,而\(a\)\(b\)实际上就是环上距离,也是很容易维护的。

这样一来,这道题就做完了。

不过这道题细节还是挺多的,具体实现可以详见代码,代码中也给出了注释。

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 10000
#define M 20000
#define LG 15
#define Gmin(x,y) (x>(y)&&(x=(y)))
#define TreeTemplate\
	int ee,lnk[P+5];struct edge {int to,nxt,val;}e[2*E+5];\
	I void Add(CI x,CI y,CI v) {e[++ee].nxt=lnk[x],e[lnk[x]=ee].to=y,e[ee].val=v;}
using namespace std;
int n,m,cnt;
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define pc(c) (C==E&&(clear(),0),*C++=c)
		#define tn (x<<3)+(x<<1)
		#define D isdigit(c=tc())
		int T;char c,*A,*B,*C,*E,FI[FS],FO[FS],S[FS];
	public:
		I FastIO() {A=B=FI,C=FO,E=FO+FS;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=tn+(c&15),D);}
		Tp I void write(Ty x) {W(S[++T]=x%10+48,x/=10);W(T) pc(S[T--]);}
		Tp I void writeln(Con Ty& x) {write(x),pc('\n');}
		I void clear() {fwrite(FO,1,C-FO,stdout),C=FO;}
}F;
template<int P,int E> class RoundSquareTree//圆方树
{
	private:
		int dep[P+5],dis[P+5],fa[P+5][LG+5];
		I int LCA(int& x,int& y)//LCA,一并求出a,b
		{
			RI i;if(dep[x]>dep[y]) for(i=0;dep[x]^dep[y];++i) (dep[x]^dep[y])>>i&1&&(x=fa[x][i]);
			else for(i=0;dep[x]^dep[y];++i) (dep[x]^dep[y])>>i&1&&(y=fa[y][i]);if(x==y) return x;
			for(i=LG;~i;--i) fa[x][i]^fa[y][i]&&(x=fa[x][i],y=fa[y][i]);return fa[x][0];
		}
	public:
		TreeTemplate;int op[N+5],V[P+5];
		I void Init(CI x=1)//初始化
		{
			RI i;for(i=1;i<=LG;++i) fa[x][i]=fa[fa[x][i-1]][i-1];
			for(i=lnk[x];i;i=e[i].nxt) e[i].to^fa[x][0]&&
				(dep[e[i].to]=dep[fa[e[i].to][0]=x]+1,dis[e[i].to]=dis[x]+e[i].val,Init(e[i].to),0);
		}
		I int Qry(CI x,CI y)//询问
		{
			RI a=x,b=y,f=LCA(a,b);if(f<=n) return dis[x]+dis[y]-(dis[f]<<1);//若f是圆点
			RI t=dis[x]-dis[a]+dis[y]-dis[b],da=dis[a]-dis[f],db=dis[b]-dis[f];
			op[a]&&(da=V[f]-da),op[b]&&(db=V[f]-db);RI dt=abs(da-db);return t+min(dt,V[f]-dt);//求出三种距离和
			//注意,dis中存储的是最短路,op表示我们原先选择的方向,此处求两点距离需要统一方向
		}
};RoundSquareTree<N<<1,M<<1> R;
template<int P,int E> class Tree
{
	private:
		int d,dfn[N+5],low[N+5],fa[N+5],dep[N+5],dis[N+5];
		I void Build(CI x,RI y,RI tot)//建立一个方点
		{
			static int T,S[N+5];RI i,t=T=dep[y]-dep[x]+1,d=0;
			S[1]=x;W(y^x) S[T--]=y,tot+=dis[y]-dis[fa[y]],y=fa[y];//存下点双中的点
			for(R.V[++cnt]=tot,i=1;i<=t;d+=dis[S[i+1]]-dis[S[i]],++i)//求出边权
				d<=tot-d?(R.op[S[i]]=0,R.Add(S[i],cnt,d),R.Add(cnt,S[i],d))//注意是最短路
				:(R.op[S[i]]=1,R.Add(S[i],cnt,tot-d),R.Add(cnt,S[i],tot-d));
		}
	public:
		TreeTemplate;
		I void Tarjan(CI x=1,CI lst=0)//Tarjan
		{
			RI i,y;for(dfn[x]=low[x]=++d,i=lnk[x];i;i=e[i].nxt) (y=e[i].to)^lst&&
			(
				!dfn[y]?(dep[y]=dep[fa[y]=x]+1,dis[y]=dis[x]+e[i].val,Tarjan(y,x),Gmin(low[x],low[y]))
				:Gmin(low[x],dfn[y]),
				low[y]>dfn[x]&&(R.Add(x,y,e[i].val),R.Add(y,x,e[i].val),0)//对于割边,直接连接两个圆点
			);
			for(i=lnk[x];i;i=e[i].nxt) (y=e[i].to)^lst&&dfn[y]>dfn[x]&&fa[y]^x&&(Build(x,y,e[i].val),0);//找点双建方点
			//显然,若一个点dfs序较大,说明由x出发走到的;而父亲并非x,说明形成了环
		}
};Tree<N,M> T;
int main()
{
	RI Qt,i,x,y,v;F.read(n),F.read(m),F.read(Qt),cnt=n;
	for(i=1;i<=m;++i) F.read(x),F.read(y),F.read(v),T.Add(x,y,v),T.Add(y,x,v);T.Tarjan(),R.Init();
	W(Qt--) F.read(x),F.read(y),F.writeln(R.Qry(x,y));return F.clear(),0;
}
posted @ 2020-05-12 12:28  TheLostWeak  阅读(266)  评论(0编辑  收藏  举报