洛谷 P3388 【模板】割点(割顶)

【题目链接】

洛谷 P3388 【模板】割点(割顶)

【题目考点】

1. 割点,点双连通分量

在学习本节前需要先学习"tarjan求强连通分量"一节,参考:【模板:强连通分量】信息学奥赛一本通 1516:消息的传递

(1) 割点及点双连通概念

割点 :无向图中,如果删掉掉一个顶点及与它相邻的所有边后,图中连通分量数量增多,则称该顶点为割点(或关节点)。

对于一个无向连通图:

如果不存在割点,则称它为点双连通图

点双连通图中:任意两条边都同在一个简单环中。除特殊情况,任意两顶点间至少存在两条顶点不重复路径。

点双连通的极大子图称为点双连通分量

(2) tarjan算法求无向图的割点及点双连通分量

无向图的DFS生成树中没有横叉边、前向边
dfn[i]:顶点i的时间戳,表示顶点i的搜索次序编号,即顶点i是第几个被访问的顶点。
low[i]:顶点i的追溯值,表示顶点i不通过父结点可以回溯到的第一个祖先结点的时间戳,也是当前顶点i所在的表示一个点双连通分量的子树的根结点的时间戳。

tarjan算法描述如下:

  1. 尝试从每个顶点开始进行深搜
  2. 首次访问顶点u时设初值 dfn[u]=low[u]=++tsts是从0开始不断增大的时间戳变量。
  3. 访问u的所有邻接点,其中v是u的一个邻接点:
    • 如果未访问过v,从v开始深搜。深搜结束后回溯时更新:low[u] = min(low[u], low[v])
    • 如果u满足割点的条件:
      (1)如果u是根结点,且有至少2个孩子,或者
      (2)u不是根结点,v是某个u的孩子,满足dfn[u] <= low[v]。
      则将u加入割点集合。
    • 如果已访问过v,那么更新low[u] = min(low[u], dfn[v])

最后输出割点集合中的顶点,即为该图的割点。

如果想求点双连通分量,需要在访问顶点u时将顶点u入栈

在深搜回溯后判断u是否为割点的位置做如下操作:

如果满足dfn[u] <= low[v],将u加入集合,不断出栈并将出栈元素加入集合,直到v出栈。该集合中的顶点构成一个点双连通分量。

【解题思路】

本题为模板题,根据上述描述实现tarjan算法。

由于一个顶点可能会被反复求出是割点,因此对求出的割点要做"去重"操作。

可以使用STL中的set类保存求出的割点,也可以使用bool类型的标记数组来标记哪个顶点是割点。

【题解代码】

解法1:tarjan算法求割点

  • 写法1:使用set保存割点
cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define N 20005
int n, m;
vector<int> edge[N];
int dfn[N], low[N], ts;
set<int> cutVer;//保存割点 
void tarjan(int u, int root)//root:当前访问dfs生成树的根结点 
{
	int child = 0;
	dfn[u] = low[u] = ++ts;
	for(int v : edge[u])
	{
		if(dfn[v] == 0)
		{
			tarjan(v, root);
			low[u] = min(low[u], low[v]); 
			if(u == root && ++child > 1 || u != root && dfn[u] <= low[v])
				cutVer.insert(u);//将u添加到割点集合中(根结点可能会被多次加入,通过set去重) 
		}
		else
			low[u] = min(low[u], dfn[v]);
	}	
}  
int main()
{
	int f, t;
	cin >> n >> m;
	for(int i = 1; i <= m; ++i)
	{
		cin >> f >> t;
		edge[f].push_back(t);
		edge[t].push_back(f);
	}
	for(int v = 1; v <= n; ++v)
		if(dfn[v] == 0)
			tarjan(v, v);
	cout << cutVer.size() << endl;
	for(int v : cutVer)
		cout << v << ' ';
	return 0;
}
  • 写法2:使用标记数组保存割点
cpp 复制代码
#include <bits/stdc++.h>
using namespace std;
#define N 20005
int n, m;
vector<int> edge[N];
int dfn[N], low[N], ts, cutVerNum;
bool cutVer[N];//保存割点 
void tarjan(int u, int root)//root:当前访问dfs生成树的根结点 
{
	int child = 0;
	dfn[u] = low[u] = ++ts;
	for(int v : edge[u])
	{
		if(dfn[v] == 0)
		{
			tarjan(v, root);
			low[u] = min(low[u], low[v]); 
			if(u == root && ++child > 1 || u != root && dfn[u] <= low[v])
				cutVer[u] = true;//将u添加到割点集合中,一个结点可能会被多次加入 
		}
		else
			low[u] = min(low[u], dfn[v]);
	}	
}  
int main()
{
	int f, t;
	cin >> n >> m;
	for(int i = 1; i <= m; ++i)
	{
		cin >> f >> t;
		edge[f].push_back(t);
		edge[t].push_back(f);
	}
	for(int v = 1; v <= n; ++v)
		if(dfn[v] == 0)
			tarjan(v, v);
	for(int i = 1; i <= n; ++i)
		if(cutVer[i])
			cutVerNum++;
	cout << cutVerNum << endl;
	for(int i = 1; i <= n; ++i) if(cutVer[i])
		cout << i << ' ';
	return 0;
}
相关推荐
xie0510_1 小时前
string模拟实现
开发语言·c++·算法
xuedingbue2 小时前
数据结构与顺序表:高效数据管理秘籍
数据结构·算法·链表
雪域迷影2 小时前
C++17中使用inline修饰类的静态成员变量
开发语言·c++·inline static·类静态成员变量
星火开发设计2 小时前
共用体 union:节省内存的特殊数据类型
java·开发语言·数据库·c++·算法·内存
求梦8202 小时前
【力扣hot100题】合并两个有序链表(22)
算法·leetcode·链表
dcmfxvr2 小时前
adwawd
算法
啊阿狸不会拉杆2 小时前
《数字信号处理 》第 7 章-无限长单位冲激响应 (IIR) 数字滤波器设计方法
数据结构·算法·信号处理·数字信号处理·dsp
IT_Octopus3 小时前
力扣热题100 20. 有效的括号
算法·leetcode
木井巳3 小时前
【递归算法】求根节点到叶节点数字之和
java·算法·leetcode·深度优先