点双连通分量和圆方树

点双连通分量

割点:如果删除这个点使得整个图不再联通,就叫割点。
点双连通图:若对于一个无向图,其任意一个节点对于这个图本身而言都不是割点,则称其点双连通。也就是说,删除任意点及其相关边后,整个图仍然属于一个连通分量。
点双连通分量:满足点双连通且极大的子图。

求法:
类似割点,考虑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 lowvdfnx,说明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

*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值