圆方树学习笔记

前置知识

  • 点双连通分量的求解方法
  • 可能一些树连剖分

圆方树的实用性

在很多图论相关的问题中,单靠我们学过的图论知识是比较难以解决问题的。相比之下,树上就有很多技巧和方法供我们选用,如树连剖分等。通常当我们在做图论无法求解时可以将其转换为树上问题,也就是对其建立圆方树。

圆方树

圆方树中有两种节点,分别为圆点和方店。圆点是在原图中出现过的点而方点是一个点双连通分量,在图中,一个圆点只与其所在的点双连通分量(方点)连边。容易发现这样建出来的一定是一棵树。

建树

\(Tarjan\) 的同时,将圆点方点链接,详见代码:

inline void tarjan(int x, int fa){  //Tarjan就点双
    dep[x] = dep[fa]+1;
    vis[x] = true;
    low[x] = dep[x];
    sta.push(x);
    int son = 0;
    for(int i = head[x]; ~i; i = edge[i].nxt)
        if(edge[i].to != fa){
            if(vis[edge[i].to])
                low[x] = min(low[x], dep[edge[i].to]);
            else{
                tarjan(edge[i].to, x);
                low[x] = min(low[x], low[edge[i].to]);
                son++;
                if(dep[x] <= low[edge[i].to]){
                    ++tot;      //加入新点
                    //将点双内的圆点分别向方点连边
                    int pre = 0;
                    while(pre != edge[i].to && !sta.empty()){
                        pre = sta.top();
                        sta.pop();
                        add_Tedge(pre, tot);
                        add_Tedge(tot, pre);
                    }
                    add_Tedge(tot, x);
                    add_Tedge(x, tot);
                }
            }
        }
    return;
}

例题:P4320 道路相遇

题目简述:给你个图,问你从 \(u\)\(v\) 中必须经过的点有几个。

思路:考虑圆方树,在建立了一后发现必须经过的点只有可能是途中的圆点,直接求圆点个数即可,这里用了一个树连剖分,然后在链上求了个前缀和。

代码:

//
//  圆方树.cpp
//  
//
//  Created by   on 2024/9/24.
//

#include <stdio.h>
#include <iostream>
#include <stack>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 5e5+2;
const int M = 1e6+2;
int n;
typedef struct{
    int to;
    int nxt;
}Edge;
Edge edge[M<<1];
int head[N];
int cnt;
inline void add_edge(int u, int v){
    edge[cnt].to = v;
    edge[cnt].nxt = head[u];
    head[u] = cnt++;
    return;
}
stack<int>sta;
bool vis[N<<1];
int dep[N<<1];
int low[N];
Edge Tedge[N<<2];
int Thead[N<<1];
inline void add_Tedge(int u, int v){
    Tedge[cnt].to = v;
    Tedge[cnt].nxt = Thead[u];
    Thead[u] = cnt++;
    return;
}
int tot;
inline void tarjan(int x, int fa){  //Tarjan就点双
    dep[x] = dep[fa]+1;
    vis[x] = true;
    low[x] = dep[x];
    sta.push(x);
    int son = 0;
    for(int i = head[x]; ~i; i = edge[i].nxt)
        if(edge[i].to != fa){
            if(vis[edge[i].to])
                low[x] = min(low[x], dep[edge[i].to]);
            else{
                tarjan(edge[i].to, x);
                low[x] = min(low[x], low[edge[i].to]);
                son++;
                if(dep[x] <= low[edge[i].to]){
                    ++tot;      //加入新点
                    //将点双内的圆点分别向方点连边
                    int pre = 0;
                    while(pre != edge[i].to && !sta.empty()){
                        pre = sta.top();
                        sta.pop();
                        add_Tedge(pre, tot);
                        add_Tedge(tot, pre);
                    }
                    add_Tedge(tot, x);
                    add_Tedge(x, tot);
                }
            }
        }
    return;
}
//树连剖分
int FA[N<<1];
int hson[N<<1];
int siz[N<<1];
inline void dfs1(int x, int fa){
    vis[x] = true;
    dep[x] = dep[fa]+1;
    FA[x] = fa;
    siz[x] = 1;
    for(int i = Thead[x]; ~i; i = Tedge[i].nxt)
        if(Tedge[i].to != fa){
            dfs1(Tedge[i].to, x);
            siz[x] += siz[Tedge[i].to];
            if(siz[Tedge[i].to] > siz[hson[x]])
                hson[x] = Tedge[i].to;
        }
    return;
}
int top[N<<1];
int pre[N<<1];  //链上前缀和
inline void dfs2(int x, int fa){
    if(!top[x])
        top[x] = x;
    if(hson[x]){
        top[hson[x]] = top[x];
        dfs2(hson[x], x);
    }
    pre[x] = pre[hson[x]] + (x <= n);
    for(int i = Thead[x]; ~i; i = Tedge[i].nxt)
        if(Tedge[i].to != fa && Tedge[i].to != hson[x])
            dfs2(Tedge[i].to, x);
    return;
}
inline int calc(int x, int y){  //求树上距离
    int sum = 0;
    while(top[x] != top[y]){
        if(dep[top[x]] > dep[top[y]]){
            sum += pre[top[x]]-pre[hson[x]];
            x = FA[top[x]];
        }
        else{
            sum += pre[top[y]]-pre[hson[y]];
            y = FA[top[y]];
        }
    }
    if(dep[x] > dep[y])
        return sum + pre[y] - pre[hson[x]];
    return sum + pre[x] - pre[hson[y]];
}
int main(){
    memset(head, -1, sizeof(head));
    memset(Thead, -1, sizeof(Thead));
    int m;
    cin >> n >> m;
    for(int i = 1; i <= m; i++){
        int u, v;
        cin >> u >> v;
        add_edge(u, v);
        add_edge(v, u);
    }
    cnt = 0;
    tot = n;
    for(int i = 1; i <= n; i++)
        if(!vis[i])
            tarjan(i, 0), sta.pop();
    memset(dep, 0, sizeof(dep));
    memset(vis, false, sizeof(vis));
    for(int i = 1; i <= tot; i++)
        if(!vis[i])
            dfs1(i, 0), dfs2(i, 0);
    int q;
    cin >> q;
    while(q--){
        int u, v;
        cin >> u >> v;
        cout << calc(u, v) << endl;
    }
    return 0;
}

posted @ 2024-09-24 10:05  HurryCine  阅读(3)  评论(0)    收藏  举报