圆方树学习

Posted Harris-H

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了圆方树学习相关的知识,希望对你有一定的参考价值。

圆方树

定义:仙人掌

仙人掌是满足每条边只在不超过 1 个简单环中的无向连通图。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-upnaGSVE-1625552006737)(C:\\Users\\HeHao\\AppData\\Roaming\\Typora\\typora-user-images\\image-20210706095225815.png)]

在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1X0yDXcy-1625552006743)(C:\\Users\\HeHao\\AppData\\Roaming\\Typora\\typora-user-images\\image-20210706095522713.png)]

在这里插入图片描述


圆方树套LCA求最短路

P5236 【模板】静态仙人掌

先利用tarjan把仙人掌转化为圆方树:

按照tarjan套路,记录每个结点的dfn序和low序。

然后对于每个子结点v,都记录一下 f a [ v ] [ 0 ] fa[v][0] fa[v][0] p r e [ v ] pre[v] pre[v],这个代码中有解释。

然后跟tarjan一样维护low。

关键部分:判断圆点的边。

l o w [ v ] > d f n [ u ] low[v]>dfn[u] low[v]>dfn[u]:则要建立一条圆点边 ( u , v , w ) (u,v,w) (u,v,w)

显然 u , v u,v u,v不在一个点双里,即不在同一个环里,不然 l o w [ v ] ≤ d f n [ u ] low[v]\\le dfn[u] low[v]dfn[u]

所以 ( u , v ) (u,v) (u,v)是一个圆点边,边权即为原图的边权。

然后建立方点边的话:

首先必须是在一个环里,然后我们特判如果 d f n [ v ] > d f n [ u ] dfn[v]>dfn[u] dfn[v]>dfn[u] v v v的父亲不是 u u u的话,我们就可以用 v v v一直跳前驱,来建立菊花边。

因为 d f n [ v ] > d f n [ u ] dfn[v]>dfn[u] dfn[v]>dfn[u],所以 v v v u u u后访问,且 u , v u,v u,v有边直接相连,且 v v v的父亲不是 u u u,说明是一个环,且 u u u是环的起点。

建图部分:

void dfs(int u,int f){	//tarjan建树
	//因为有环所以要有f这个参数记录父亲,避免在环里一直遍历
	dfn[u]=low[u]=++id;
	for(int i=h[u];i;i=e[i].nt){
		int v=e[i].to,w=e[i].w;
		if(v==f) continue;
		if(!dfn[v]){
			fa[v][0]=u;	//记录父亲
			pre[v]=w;	//记录前驱边权,便于后面对环的处理.
			dfs(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u]){	//建圆点.	V_circle
				add(u,v,w,1);
			}
		}
		else low[u]=min(low[u],dfn[v]);
	}
	for(int i=h[u];i;i=e[i].nt){	//建方点 V_square
		int v=e[i].to,w=e[i].w;
		if(u==fa[v][0]||dfn[v]<=dfn[u]) continue;
		square(u,v,w);
	}
}

然后在建立菊花边的时候,我们需要维护一个 s u m [ ] sum[] sum[]数组,用来计算菊花边的边权。

先将 v v v往前跳,每跳一次就更新当前结点的环上前缀和。

最后 s u m [ u ] sum[u] sum[u]就是整个环的和,然后把它赋给一个新的结点,这个点也就是该环的方点。

然后 s u m [ u ] = 0 sum[u]=0 sum[u]=0

然后再跑一遍环,与方点建立边。边权就是取环上到方点更近的一段距离,取两端距离的最小值。

方点建边部分:

void square(int u,int v,int w){
	int p=v;
	int wsum=w;
	while(p!=fa[u][0]){
		sum[p]=wsum;
		wsum+=pre[p];
		p=fa[p][0];
	}
	sum[++tot]=sum[u];sum[u]=0;
	p=v;
	while(p!=fa[u][0]){
		int mn=min(sum[p],sum[tot]-sum[p]);
		add(tot,p,mn,1);
		p=fa[p][0];
	}
}

后面就是比较裸的倍增 L C A LCA LCA了,也可以用树剖 L C A LCA LCA,这里用倍增 L C A LCA LCA比较方便求距离,因为后面要特判一下当 l c a lca lca为方点的情况。

首先是基础的预处理 f a [ u ] [ 0 ] fa[u][0] fa[u][0] d e p [ ] dep[] dep[]数组 ,然后更新 d i s [ ] dis[] dis[]数组。

dfs1部分:

void dfs1(int u,int f){
	dep[u]=dep[f]+1;
	fa[u][0]=f;
	for(int i=h1[u];i;i=e1[i].nt){
		int v=e1[i].to;
		if(v==f) continue;
		d[v]=d[u]+e1[i].w;
		dfs1(v,u);
	}
}

然后就是求 l c a lca lca部分。

在这之前需要先预处理号fa数组,用倍增递推即可。

先是基础的倍增找到lca。

对于lca为圆点则直接求。

如果lca为方点,因为是环,直接套原来lca的公式是不行的。

考虑找到lca的两个儿子A,B,这两个儿子A,B分别是x,y的祖先。

然后答案就是dis(x,A)+dis(A,B)+dis(y,B)

dis(A,B) 就是之前我们预处理的环上两点的更近的距离取最小值。

也即: m i n ( a b s ( s u m [ A ] − s u m [ B ] ) , s u m [ l c a ] − a b s ( s u m [ A ] − s u m [ B ] ) ) min(abs(sum[A]-sum[B]),sum[lca]-abs(sum[A]-sum[B])) min(abs(sum[A]sum[B]),sum[lca]abs(sum[A]sum[B]))

ll fun(int x,int y){
	int u=x,v=y;
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=20;~i;i--)
		if(dep[fa[u][i]]>=dep[v]) u=fa[u][i];
	int lca;
	if(u==v) lca=u;
	else {
		for(int i=20;~i;i--)
			if(fa[u][i]!=fa[v][i]) u=fa[u][i],v=fa[v][i];
		lca=fa[u][0];
	}
	ll tmp=d[x]+d[y]-(d[lca]<<1);
	if(lca>n){
		tmp-=d[u]-d[lca]+d[v]-d[lca];
		tmp+=min(abs(sum[u]-sum[v]),sum[lca]-abs(sum[u]-sum[v]));
	}
	 return tmp;
}

总的时间复杂度: O ( ( n + m ) + q l o g n ) O((n+m)+qlogn) O((n+m)+qlogn)

code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull; 
const int N=2e4+5,M=2e4+5,inf=0x3f3f3f3f,mod=1e9+7;
#define mst(a,b) memset(a,b,sizeof a)
#define PII pair<int,int>
#define fi first
#define se second
#define pb emplace_back
#define SZ(a) (int)a.size()
#define ios ios::sync_with_stdio(false),cin.tie(0) 

struct edge{
	int to,nt,w;
}e[N<<1],e1[N<<1];
int h[N],h1[N],cnt,cnt1;
void add(int u,int v,int w,int f){
	if(!f){
		e[++cnt]={v,h[u],w},h[u]=cnt;
		e[++cnt]={u,h[v],w},h[v]=cnt;
	}
	else {
		e1[++cnt1]={v,h1[u],w},h1[u]=cnt1;
		e1[++cnt1]={u,h1[v],w},h1[v]=cnt1;
	}
}
int dfn[N],low[N],id,tot;
int pre[N],fa[N][22],dep[N];
int n,m,q;
ll sum[N],d[N];
void square(int u,int v,int w){
	int p=v;
	int wsum=w;
	while(p!=fa[u][0]){
		sum[p]=wsum;
		wsum+=pre[p];
		p=fa[p][0];
	}
	sum[++tot]=sum[u];sum[u]=0;
	p=v;
	while(p!=fa[u][0]){
		int mn=min(sum[p],sum[tot]-sum[p]);
		add(tot,p,mn,1);
		p=fa[p][0];
	}
}
void dfs(int u,int f){	//tarjan建树
	//因为有环所以要有f这个参数记录父亲,避免在环里一直遍历
	dfn[u]=low[u]=++id;
	for(int i=h[u];i;i=e[i].nt){
		int v=e[i].to,w=e[i].w;
		if(v==f) continue;
		if(!dfn[v]){
			fa[v][0]=u;	//记录父亲
			pre[v]=w;	//记录前驱边权,便于后面对环的处理.
			dfs(v,u);
			low[u]=min(low[u],low[v]);
			if(low[v]>dfn[u]){	//建圆点.	V_circle
				add(u,v,w,1);
			}
		}
		else low[u]=min(low[u],dfn[v]);
	}
	for(int i=h[u];i;i=e[i].nt){	//建方点 V_square
		int v=e[i].to,w=e[i].w;
		if(u==fa[v][0]||dfn[v]<=dfn[u]) continue;
		square(u,v,w);
	}
}
void dfs1(int u,int f){
	dep[u]=dep[f]+1;
	fa[u][0]=f;
	for

以上是关于圆方树学习的主要内容,如果未能解决你的问题,请参考以下文章

圆方树小结

圆方树总结

Tourists——圆方树

圆方树与仙人掌

BZOJ2125: 最短路 圆方树(静态仙人掌)

luogu4320道路相遇 (圆方树 + LCA)