点双连通分量
割点:如果删除这个点使得整个图不再联通,就叫割点。
点双连通图:若对于一个无向图,其任意一个节点对于这个图本身而言都不是割点,则称其点双连通。也就是说,删除任意点及其相关边后,整个图仍然属于一个连通分量。
点双连通分量:满足点双连通且极大的子图。
求法:
类似割点,考虑tarjan算法
l
o
w
v
low_v
lowv和
d
f
n
x
dfn_x
dfnx的关系,如果
l
o
w
v
≥
d
f
n
x
low_v\ge dfn_x
lowv≥dfnx,说明dfs子树内的点“翻不上去”,所以删除x之后整个子树都被孤立了,也就是说x是割点,x以下的子树(包括x)构成一个点双连通分量,注意用栈维护点的时候,x本身不能出栈,因为两个点双可以有一个交点,比如8这种形状的中间那个点。
void Tarjan(int x,int fa)
{
dfn[x]=low[x]=++num;
s.push(x);
int ch_num=0;
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (!dfn[v])
{
Tarjan(v,x);
ch_num++;
low[x]=min(low[x],low[v]);
if (low[v]>=dfn[x])
{
is_cut[x]=1;
v_dcc++;
while (s.top()!=v) node[v_dcc].push_back(s.top()),s.pop();
node[v_dcc].push_back(v),s.pop();
node[v_dcc].push_back(x);
}
}
else if (v!=fa) low[x]=min(low[x],dfn[v]);
}
if (!fa && !ch_num) is_cut[x]=1,node[++v_dcc].push_back(x);
}
圆方树
对于每一个点双,建出一个方点表示这个点双,连向点双内的所有点。原图的点称为圆点,这样可以构成一棵树。那么一个点双会对应变成一个菊花,对于上面提到的8这种情况,也就是两个点双有一个交点的情况,新图上表现为两个菊花共用一个圆点。
圆方树有一些好的性质:
- 两个点之间的必经点,是圆方树上两点间圆点的个数,因为点双内有至少两条路,转换到圆方树上就是可以经过一次方点,走到所有和方点连着的点(也就是在点双内随便走),但是在跨越两个点双的时候,那个交点式必经之路。
- 圆点不会直接和圆点相连。
例题:洛谷P4320 道路相遇
题目链接
大意:模板题,求图上两点之间必经点的个数。多组询问。
思路:建出圆方树之后LCA即可。倍增/树剖都行。
#include<bits/stdc++.h>
#define pa pair<int,int>
#define INF 0x3f3f3f3f
#define inf 0x3f
#define fi first
#define se second
#define mp make_pair
#define ll long long
#define ull unsigned long long
#define pb push_back
using namespace std;
inline ll read()
{
ll f=1,sum=0;char c=getchar();
while (!isdigit(c)) {if (c=='-') f=-1;c=getchar();}
while (isdigit(c)) {sum=sum*10+c-'0';c=getchar();}
return sum*f;
}
const int MAXN=1000010;
const int MAXM=1000010;
struct edge{
int next,to;
}e[MAXM*2],e2[MAXN*4];
int head[MAXN],cnt;
void addedge(int u,int v)
{
e[++cnt].next=head[u];
e[cnt].to=v;
head[u]=cnt;
}
int head2[MAXN*2],cnt2;
void addedge2(int u,int v)
{
// cout<<u<<'-'<<v<<endl;
e2[++cnt2].next=head2[u];
e2[cnt2].to=v;
head2[u]=cnt2;
}
int dfn[MAXN],low[MAXN],num;
stack <int> st;
int f_num;
void Tarjan(int x,int fa)
{
dfn[x]=low[x]=++num;
st.push(x);
for (int i=head[x];i;i=e[i].next)
{
int v=e[i].to;
if (!dfn[v])
{
Tarjan(v,x);
low[x]=min(low[x],low[v]);
if (low[v]>=dfn[x])
{
f_num++;
while (st.top()!=v)
{
addedge2(f_num,st.top());
addedge2(st.top(),f_num);
st.pop();
}
addedge2(f_num,v),addedge2(v,f_num);
st.pop();
addedge2(f_num,x),addedge2(x,f_num);
}
}
else if (v!=fa) low[x]=min(low[x],dfn[v]);
}
}
const int LOG=25;
int fa[MAXN][LOG],g[MAXN][LOG],n,deep[MAXN];
void dfs(int x,int faa)
{
// cout<<x<<endl;
for (int i=head2[x];i;i=e2[i].next)
{
int v=e2[i].to;
if (v==faa) continue;
fa[v][0]=x;
deep[v]=deep[x]+1;
g[v][0]=((v<=n)?1:0);
dfs(v,x);
}
}
void build()
{
for (int j=1;j<LOG;j++)
for (int i=1;i<=n;i++)
{
fa[i][j]=fa[fa[i][j-1]][j-1];
g[i][j]=g[i][j-1]+g[fa[i][j-1]][j-1];
}
}
int LCA(int u,int v)
{
int ret=0;
if (deep[u]>deep[v]) swap(u,v);
int d=deep[v]-deep[u];
for (int i=0;i<LOG;i++)
if (d&(1<<i))
ret+=g[v][i],v=fa[v][i];
// cout<<u<<' '<<v<<endl;
if (u==v)
{
if (u<=n) ret++;
return ret;
}
for (int i=LOG-1;i>=0;i--)
{
if (fa[u][i]!=fa[v][i])
{
ret+=g[u][i];
ret+=g[v][i];
u=fa[u][i];
v=fa[v][i];
}
}
ret+=g[u][0],ret+=g[v][0];
if (fa[u][0]<=n) ret++;
return ret;
}
int main()
{
n=read();int m=read();
f_num=n;
for (int i=1;i<=m;i++)
{
int u=read(),v=read();
addedge(u,v),addedge(v,u);
}
Tarjan(1,0);
fa[1][0]=1;
// cout<<"!!"<<endl;
dfs(1,0);
build();
int q=read();
while (q--)
{
int u=read(),v=read();
int ans=LCA(u,v);
cout<<ans<<'\n';
}
return 0;
}
/*
5 5
1 2
1 3
1 4
3 4
4 5
1
4 5
*/