圆方树学习笔记

圆方树

圆方树是将仙人掌图(每条边在不超过一个简单环的无向图中)转化为树的一种工具

建树方式

1、原图中的点我们称为圆点。

2、对于每一个环,我们将其转化为菊花图,新增一个节点作为菊花点,这个节点称为方点。(这个菊花点我们选取环的一个割点与其等价)。

3、去除所有环的边,保留其他边以及菊花图的边。

4、边权的处理:不在环上的边保留原边权。菊花点与每个点的边权为菊花点到每个点的最短路。

比如下面这个图

在这里插入图片描述

建完树应该是这个样子 6号节点为方点

在这里插入图片描述

代码实现

圆点即原图的点,我们不需要考虑

对于方点,我们在跑 t a r j a n tarjan tarjan 的时候判环 然后建点连边

对于原图中的非环边 我们直接在新图上连接即可

非环边 容易发现它是割边 t a r j a n tarjan tarjan 的时候进行判断

对于环边 先判环 求出环的起点和终点前的一个点 然后维护每个点的一个父亲 进行连边赋权即可

如何判断环的起点和终点前一个点:

对于环的起点 访问序一定是最小的 即 d f n [ u ] < d f n [ v ] dfn[u]<dfn[v] dfn[u]<dfn[v]

那我们只需要遍历 u u u 的儿子 如果 d f n [ u ] < d f n [ v ] dfn[u]<dfn[v] dfn[u]<dfn[v] 并且 f a [ v ] ≠ u fa[v]\neq u fa[v]=u 那么 u , v {u,v} u,v 就是一组环的起始点

当然第一个条件也可以换成 l o w [ v ] = d f n [ u ] low[v]=dfn[u] low[v]=dfn[u] 因为对于环的末尾节点 它的时间戳一定可以回溯到起点

void addsqare(int u,int v,int w){
    int tv=v;
    ++n;
    while(tv!=A.fa[u].first){
        B.sum[tv]=w;
        w+=A.fa[tv].second;
        tv=A.fa[tv].first;
    }
    B.sum[n]=B.sum[u];
    tv=v;
    while(tv!=A.fa[u].first){
        B.add(n,tv,min(B.sum[tv],B.sum[n]-B.sum[tv]));
        B.add(tv,n,min(B.sum[tv],B.sum[n]-B.sum[tv]));
        tv=A.fa[tv].first;
    }
}
void tarjan(int x,int pre){
    A.dfn[x]=A.low[x]=++A.time;
    // cout<<x<<'\n';
    for(int i=A.h[x];i;i=A.e[i].nxt){
        int to=A.e[i].to,w=A.e[i].w;
        if(to==pre)continue;
        if(!A.dfn[to]){
            A.fa[to]={x,w};
            tarjan(to,x);
            A.low[x]=min(A.low[x],A.low[to]);
        }
        else A.low[x]=min(A.low[x],A.dfn[to]);
        if(A.low[to]>A.dfn[x]){
            B.add(x,to,w);
            B.add(to,x,w);
        }
        // if(A.fa[to].first!=x&&A.dfn[x]<A.dfn[to])addsqare(x,to,w);
        if(A.fa[to].first!=x&&A.low[to]==A.dfn[x])addsqare(x,to,w);
    }
}

A为原无向图 B为新树

a d d s q a r e addsqare addsqare 函数为建方点 并连边赋权值

P5236 【模板】静态仙人掌

给你一个有 n n n 个点和 m m m 条边的仙人掌图,和 q q q 组询问
每次询问两个点 u , v u,v u,v,求两点之间的最短路。

保证输入数据没有重边。

题目分析

对仙人掌建立圆方树

那么 u , v u,v u,v 间的最短路 即 u → l c a ( u , v )   +   v → l c a ( u , v ) u\rightarrow lca(u,v) \ + \ v \rightarrow lca(u,v) ulca(u,v) + vlca(u,v)

需要注意的是

如果 l c a ( u , v ) lca(u,v) lca(u,v) 为方点 那么说明 u → v u \rightarrow v uv 的最短路径需要经过这个方点所在的环 由于我们方点的创建是根据环的一个割点 所以直接作和未必是最短的 比如下面这个图

在这里插入图片描述

1 → 6 1\rightarrow 6 16 的最短路 一定会经过这个环 即他们的 l c a lca lca 一定是方点

容易发现最短路是 1 → 2 → 3 → 6 1 \rightarrow 2 \rightarrow 3 \rightarrow 6 1236

2、3、5均为割点 都可作为建立方点的基准点 但选取5作为基准点按上述方式算出的最短路要大

因为5作为割点 那么 1 → 6 1\rightarrow6 16 一定会经过5 但实际上最短路不需要经过5

那这种情况如何处理呢

我们只需要处理出 l c a ( u , v ) lca(u,v) lca(u,v) 在环上 u , v u,v u,v 方向上的两个儿子 那么经过环的路只有两条 取最小值即可

在图上的即为 2 → 3 2 \rightarrow 3 23 2 → 4 → 5 → 3 2\rightarrow4 \rightarrow5\rightarrow3 2453

代码实现

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<iomanip>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<stack>
#include<cassert>
//#include<random>
//#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int,int>
#define lowbit(x) x&-x
struct node{
    int to,nxt,w;
};
int n,m,q,tn;
struct Graph{
    vector<int>h,dfn,low,sum;
    vector<node>e;
    vector<pii>fa;
    int cnt=0,time=0;
    Graph(){}
    Graph(int n,int m){
        e=vector<node>(2*m+10);
        h=vector<int>(2*n+10,0);
        sum=dfn=low=h;
        fa=vector<pii>(n+10,{0,0});
    }
    void add(int u,int v,int w){
        e[++cnt].nxt=h[u];
        h[u]=cnt;
        e[cnt].to=v;
        e[cnt].w=w;
    }
}A,B;
void addsqare(int u,int v,int w){
    int tv=v;
    ++n;
    while(tv!=A.fa[u].first){
        B.sum[tv]=w;
        w+=A.fa[tv].second;
        tv=A.fa[tv].first;
    }
    B.sum[n]=B.sum[u];
    tv=v;
    while(tv!=A.fa[u].first){
        B.add(n,tv,min(B.sum[tv],B.sum[n]-B.sum[tv]));
        B.add(tv,n,min(B.sum[tv],B.sum[n]-B.sum[tv]));
        tv=A.fa[tv].first;
    }
}
void tarjan(int x,int pre){
    A.dfn[x]=A.low[x]=++A.time;
    // cout<<x<<'\n';
    for(int i=A.h[x];i;i=A.e[i].nxt){
        int to=A.e[i].to,w=A.e[i].w;
        if(to==pre)continue;
        if(!A.dfn[to]){
            A.fa[to]={x,w};
            tarjan(to,x);
            A.low[x]=min(A.low[x],A.low[to]);
        }
        else A.low[x]=min(A.low[x],A.dfn[to]);
        if(A.low[to]>A.dfn[x]){
            B.add(x,to,w);
            B.add(to,x,w);
        }
        // if(A.fa[to].first!=x&&A.dfn[x]<A.dfn[to])addsqare(x,to,w);
        if(A.fa[to].first!=x&&A.low[to]==A.dfn[x])addsqare(x,to,w);
    }
}
int dep_v[200020],dep[200020];
int fa[200020][21];
void dfs(int x,int pre){
    fa[x][0]=pre;
    for(int j=1;j<=19;j++){
        fa[x][j]=fa[fa[x][j-1]][j-1];
    }
    for(int i=B.h[x];i;i=B.e[i].nxt){
        int to=B.e[i].to,w=B.e[i].w;
        if(to==pre)continue;
        dep[to]=dep[x]+1;
        dep_v[to]=dep_v[x]+w;
        dfs(to,x);
    }
}
int cal(int u,int v){
    int ans=0;
    if(dep[u]<dep[v])swap(u,v);
    int len=dep[u]-dep[v];
    int tu=u,tv=v;
    while(len){u=fa[u][__builtin_ffs(len)-1];len^=lowbit(len);}
    ans+=dep_v[tu]-dep_v[u];
    if(u==v)return ans;
    ans+=dep_v[u]+dep_v[v];
    for(int i=__lg(dep[u]);i>=0;i--){
        if(fa[fa[u][i]][0]!=fa[fa[v][i]][0]){
            u=fa[u][i],v=fa[v][i];
        }
    }
    if(fa[u][0]!=fa[v][0])u=fa[u][0],v=fa[v][0];
    if(fa[u][0]<=tn)return ans-2*dep_v[fa[u][0]];
    if(B.sum[u]<B.sum[v])swap(u,v);
    return ans-dep_v[u]-dep_v[v]+min(B.sum[u]-B.sum[v],B.sum[fa[u][0]]-(B.sum[u]-B.sum[v]));
}
void solve(){
    int q;
    cin>>n>>m>>q;
    tn=n;
    A=Graph(n,m);
    B=Graph(2*n,m);
    for(int i=1;i<=m;i++){
        int u,v,w;
        cin>>u>>v>>w;
        A.add(u,v,w);
        A.add(v,u,w);
    }
    tarjan(1,0);
    dfs(1,0);
    while(q--){
        int u,v;
        cin>>u>>v;
        cout<<cal(u,v)<<'\n';
    }
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int t=1;
    // cin>>t;
    while(t--)solve();
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值