P3388 【模板】割点(割顶) 题解

博客园同步

原题链接

简要题意:

给定一个图,求所有割点。

割点(割顶)的定义:去掉该点整个图不连通。

前置知识:

强连通分量的 Tarjan\texttt{Tarjan}Tarjan 求法。

不懂的可以先去了解下

本题作为 Tarjan\texttt{Tarjan}Tarjan 求割点的模板题。

首先,我们同样和求强连通分量一样,搞出一个 dfn\text{dfn}dfn 和 low\text{low}low.

接着,你会发现这样的情况:

如果 xxx 节点后面搜索树上的点 yyy 都满足 lowyxlow_y \geq xlowy​≥x,此时 xxx 不出意外 是一个割点。

那 “意外” 指什么?

想一下,如果是一条链的链顶,同样也满足 lowyx(ySubtree(x))low_y \geq x(y \in \texttt{Subtree(x)})lowy​≥x(y∈Subtree(x)),但它不是割点。

因此,我们从 ppp 节点开始搜索,就要判断清楚,ppp 到底是不是?

应该是这样的:如果 ppp 有超过 111 个儿子,说明把它弄掉之后那 >1>1>1 个儿子走不通,所以 ppp 是割点。

否则 1\leq 1≤1 个儿子显然不是割点,这是要特殊判断的。

答案如何记录?你注意到需要将答案去重,排序(因为一个点可能被它的每一个子树重复的记录多次)。那么显然就是用 set\texttt{set}set 解决!

智商不够,数据结构来凑

set\texttt{set}set 的到来凭空给时间增加一个 log\texttt{log}log,但是没有关系,因为本来 Tarjan\texttt{Tarjan}Tarjan 就是线性的。

(其实明明可以先哈希的,可是用 STL\text{STL}STL 多快乐啊·)

注意细节即可通过。

时间复杂度:O(nlogn+m)O(n \log n + m)O(nlogn+m).

实际得分:100pts100pts100pts.

#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;

const int N=1e5+1;

inline int read(){char ch=getchar();int f=1;while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
	int x=0;while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();return x*f;}

int low[N],dfn[N],fa[N],n,m,cnt=0;
bool vis[N]; set<int>s;
vector<int>G[N];

inline void Tarjan(int u) {
	int sum=0; vis[u]=1; //sum 是子树个数
	dfn[u]=low[u]=++cnt;
	for(int i=0;i<G[u].size();i++) {
		int v=G[u][i];
		if(!vis[v]) {
			fa[v]=u; sum++; Tarjan(v); //父亲节点用来判断起始点
			low[u]=min(low[u],low[v]);
			if(fa[u]!=u && low[v]>=dfn[u]) s.insert(u); //先把起始点排除
		} else if(v!=fa[u]) low[u]=min(low[u],dfn[v]); //同样记录
	} if(fa[u]==u && sum>=2) s.insert(u); //最后判断起始点
}

int main(){
	n=read(),m=read(); while(m--) {
		int x=read(),y=read();
		G[x].push_back(y);
		G[y].push_back(x);
	} for(int i=1;i<=n;i++)
		if(!vis[i]) {
			fa[i]=i; //表示从 i 节点开始搜索
			Tarjan(i);
			cnt=0;
		}
	cout<<s.size()<<endl;
	for(set<int>::iterator i=s.begin();i!=s.end();i++)
		printf("%d ",*i); //记得是空格隔开,不是答案换行
	return 0;
}

上一篇:2019.8.25刷题统计


下一篇:【luogu3388】 【模板】割点(割顶)[tarjan 割点]