P3388 【模板】割点(割顶)题目链接:洛谷
tarjan遍历过程视频链接
1.图用dfs的遍历。dfs对图,就会形成树 (写dfs代码时刻要有"栈"的思想)
2.Tarjan算法求非强连通图,主要用到了两个数组,dfn和low数组。
3.在dfs中如何加东西:
dfs(i)后面语句怎么写?每次dfs完成后,就是对前面的影响,
像栈一样。
4.无向图实际是 在无向树的基础上节点添加边。
连通图定义:
在无向图中,一个连通图中任意两点均可到达,称为连通图。
割点的定义:
在一个无向图里,去掉一个顶点,及其去掉该点的所有边,剩下的图
不连通,那么这个点就是个割点。
割边的定义: (不会不会,以后再补 。)
在一个无向图里,去掉一条边,图就不连通了。
举个栗子:下图的无向图的割点为:0,3。
ps:以下两图来自上文视频链接截下来的图。
在下图 上面数字dfn数组,下面数字low数组。
用了一个栈更好理解。
其中满足low[u]=dfn[u],退栈到u.
dfn中是时间戳是,1-2-3-4
然后回退到2 , 2-5
再回退到1, 1到6.
最后回退到1.结束。
强连通分量怎么就有了呢?
在退栈2时,进栈5,判断1时,low[5]=dfn[1];
之后在5中退出。
有个5-1,形成了一个1-2-5-1的环,这不就成了一个强连通分量了。
之后就是这么执行。
下面图举例(方便理解后续的代码): 我画成树的样子,再在节点随意连,就成了无向图。
1号是该的根节点 :那么子节点是两个, 就不是三个了?
为什么呢?
原因是: 我们在遍历的时候深度搜索,搜到3时,3就搜到4,4被标记了,退回到1节点,4节点已经标记了,1节点就不会对4搜。
所以1节点的子节点是2个。
所以只要根节点的子节点有两个及以上,根节点就是割点。(这里的子节点要细心些)。
样例输入:
上图。
题解代码如下
代码如下:
割点针对无向图!!
#include
#include
#include
using namespace std;
int read(){
int s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
const int maxn=2e5+3;
int low[maxn],dfn[maxn]; //dfn:"时间戳"
int visit[maxn]; //点的有无访问
int index;
vector<int> a[maxn]; //邻接表
int fa[maxn]; //
set<int> s;
inline void tarjan(int u){
int sum=0;
visit[u]=1;
dfn[u]=low[u]=++index;
for(int i=0;i<a[u].size();i++){
int v=a[u][i]; //u到v。
if(!visit[v]){
fa[v]=u; // 父亲节点用来判断起始点
sum++;
tarjan(v);
//u后面的dfs完成了,执行下面语句时在v顶点要做的事都完成了
//现在开始完成u的事,根据v的事完成u的事。
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]);
//dfn[v] 会更好些,有点像能唯一确定他组成一个环的先祖吧
//low[v] 写起也怪怪的
}
//根特判定的原因,根就没先祖了,也就用不了low,dfn判定了。
//判断是无割点:子节点两个及以上就是割点。
if(fa[u]==u&&sum>=2) s.insert(u);
}
int main (){
int n,m;
n=read();m=read();
int u,v;
for(int i=1;i<=m;i++){
u=read();v=read();
a[u].push_back(v);
a[v].push_back(u);
}
for(int i=1;i<=n;i++){
if(!visit[i]){
fa[i]=i;//父节点赋初值
tarjan(i);
index=0;
}
}
cout<<s.size()<<endl;
for(set<int>::iterator i=s.begin();i!=s.end();i++){
cout<<*i<<" ";
}
return 0;
}
思考一下没s.insert(u),u会不会被重复的添加呢?
没错,当然会重复咯,不然为啥用set集合呢!,是吧 。
改一下tarjan函数
如下:
那就用ans数组记录下每个割点加了几次。
int ans[maxn];
inline void tarjan(int u){
int sum=0;
int num=0;
visit[u]=1;
dfn[u]=low[u]=++index;
for(int i=0;i<a[u].size();i++){
int v=a[u][i];
if(!visit[v]){
fa[v]=u;
sum++;
tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]) {num++;s.insert(u);
//cout<
}
} else //if(v!=fa[u])
low[u]=min(low[u],dfn[v]);
}
//if(fa[u]==u&&sum>=2) {num++;s.insert(u);}
if(fa[u]!=u) num++;
//if(u==1) cout<
ans[u]=num;
}
ans数组结果如下 2 1 2 2 1 2
咦!!!!细心的你发现了,咋就1加了两次呢???
原来顶点1是树的根呀(dfs嘛,就是图的遍历,那就树咯),1都是根了,就没有父节点。low[1]=dfn[1]=1; 所以会加一次。
这就是为啥在题解代码里会有对根节点的特判。
那么根节点怎么才能是割点呢。问到点子上了。
2020小米选拔赛第一场D题:Router Mesh
题意:
无向图n点m边
删除任意一点后,有多少个连通图
思路:
首先得知道图不一定是连通的喔。就会牵扯到有好几个连通图。
上文也讲到了ans数组。
嗯哼,派上用场啦 . . . . . . ...... ......
删除一点,那他的所有边也会被删掉。那会增加几个连通图呢??
那不得增加ans[i]个咯。
题解代码如下:
#include
#include
#include
using namespace std;
int read(){
int s = 0, f = 1; char ch = getchar();
while(!isdigit(ch)){
if(ch == '-') f = -1;
ch = getchar();
}
while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
return s * f;
}
const int maxn=4e5+3;
int low[maxn],dfn[maxn];
int visit[maxn];
int index;
vector<int> a[maxn];
int fa[maxn];
set<int> s;
int ans[maxn];
inline void tarjan(int u){
int sum=0;
int num=0;
visit[u]=1;
dfn[u]=low[u]=++index;
for(int i=0;i<a[u].size();i++){
int v=a[u][i];
if(!visit[v]){
//删去了对根节点的特判,根节点会进入s集合里,如果他不是割点就只进一次。
fa[v]=u;
sum++;
tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]) {num++;s.insert(u);}
} else //if(v!=fa[u])
low[u]=min(low[u],dfn[v]);
}
//if(fa[u]==u&&sum>=2) {num++;s.insert(u);}
//为啥要在这样还要num++呢,嗯。。。玄学。
if(fa[u]!=u) num++;
//if(u==1) cout<
ans[u]=num;
}
int main (){
int n,m;
n=read();m=read();
int u,v;
for(int i=1;i<=m;i++){
u=read();v=read();
a[u].push_back(v);
a[v].push_back(u);
}
int num=0;
for(int i=1;i<=n;i++){
if(!visit[i]){
num++;
fa[i]=i;
tarjan(i);
index=0;
}
}
/*cout<::iterator i=s.begin();i!=s.end();i++){
cout<<*i<<" ";
}*/
//cout<
for(int i=1;i<=n;i++){
//在这里ans[i]减了1
//对应了这条代码if(fa[u]!=u) num++;
//也就是根节点不加,非根节点要加1。
cout<<ans[i]-1+num<<" ";
}
return 0;
}