圆方树学习
Posted Harris-H
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了圆方树学习相关的知识,希望对你有一定的参考价值。
圆方树
定义:仙人掌
仙人掌是满足每条边只在不超过 1 个简单环中的无向连通图。
圆方树套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以上是关于圆方树学习的主要内容,如果未能解决你的问题,请参考以下文章