圆方树学习笔记
前置知识
- 点双连通分量的求解方法
- 可能一些树连剖分
圆方树的实用性
在很多图论相关的问题中,单靠我们学过的图论知识是比较难以解决问题的。相比之下,树上就有很多技巧和方法供我们选用,如树连剖分等。通常当我们在做图论无法求解时可以将其转换为树上问题,也就是对其建立圆方树。
圆方树
圆方树中有两种节点,分别为圆点和方店。圆点是在原图中出现过的点而方点是一个点双连通分量,在图中,一个圆点只与其所在的点双连通分量(方点)连边。容易发现这样建出来的一定是一棵树。
建树
在 \(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;
}

浙公网安备 33010602011771号