点双连通分量和割点的tarjan算法

某蒟蒻实在是不会写割点与点双的代码了,所以写篇博客恶补。

主要参考:割点(tarjan)点双

先说一波割点的 tarjan 算法

tarjan 算法在很多地方都有应用

例如求强连通分量的 tarjan ,还有 LCA 的 tarjan

虽说这两个不一样,但是大体思路相通

都是利用了时间戳( dfs 搜索节点的顺序,下文用 dfn 表示)和不经过父节点能回溯到的最早的祖宗(最早即时间戳最小,下文用 low 表示)

默认 low[u] = dfn[u]

在继续 dfs 的过程中,若v未搜索过,需用low[u] = min(low[u], low[v])来对low[u]进行更新

若 v 是已经搜索过的,则肯定 dfn[v]<dfn[u] 成立,用 low[u] = min(low[u], dfn[v]) 对 low[u] 进行更新( v 不为 u 父亲时)

首先对 u 是根节点时进行判断,再者就是对非根节点的维护

代码借用的是第一篇博客的(是我太懒了)但根据其后文做出了些许改动

const int V = 20;
int dfn[V],low[V],parent[V];
bool ap[V];
vector<int>g[V];
void dfs(int u){
    int count = 0;
    // 子树数量
    int children = 0;
    // 默认low[u]等于dfn[u]
    dfn[u] = low[u] = ++count;
    // 遍历与u相邻的所有顶点
    for(int v:g[u]){
        // (u, v)为树边
        if (!dfn[v]){
            // 递增子树数量
            children++;
            // 设置v的父亲为u
            parent[v] = u;
            // 继续DFS
            dfs(v);
            // DFS完毕,low[v]已求出,如果low[v]<low[u]则更新low[u]
            low[u] = min(low[u],low[v]);
            // 如果是根节点且有两棵以上的子树则是割点 如果不是根节点且low[v]>=dfn[u]则是割点
            if(parent[u]==-1&& children>=2||parent[u]!=-1&&low[v]>=dfn[u]&&!ap[u]){
                cout<<"Articulation point: "<<u<<endl;
                ap[u] = true;
            }
        }
        // (u, v)为回边,且v不是u的父亲
        else if(v!=parent[u])
            low[u] = min(low[u],dfn[v]);
    }
}
//以下为我自己写的
void dfs(int u,int fa){
	int son = 0;
    // 子树数量
	dfn[u] = low[u] = ++tcnt;
	for(int i = 0;i<g[u].size();i++){
		int v = g[u][i];
        // (u, v)为树边
		if(dfn[v]==0){
			son++;
			dfs(v,u);
            // 继续DFS
			low[u] = min(low[u],low[v]);
            //low[v]>=dfn[u]则是割点
			if(low[v]>=dfn[u])
				flag[u] = 1;
		}else if(v!=fa){
			low[u] = min(low[u],dfn[v]);
		}
	}
    // 如果是根节点且有两棵以上的子树则是割点,只有一棵子树则不是
	if(u==root&&son==1)
		flag[u] = 0;
}

应该没有把他改成了一段错误代码吧?

再说一说点双,第二篇博客中对与点双有一个很好的定义

点双联通的: 对于一个无向图,假如仅仅对于该图而言其中不包含割点,那么称这个图是点双连通的。

点双连通分量: 对于一个无向图中的极大点双连通的子图,我们称这个子图为点双连通分量。

点双的性质有:

1、 任意两点间都存在至少两条点不重复路径。

比较特殊的点双:只有两个点一条边的点双(原文有图,我偷懒就不放了)

网上大部分以该性质作为点双的定义,然后说这种图虽然不满足定义(此处指这个性质),但是是一个特殊的点双。而上面那个更严谨的定义则可以将这种情况包含在内,更加完备。

2、 图中任意一个割点都在至少两个点双中。

因为删去割点后图会不连通,所以割点至少连接着图的两部分,而由于点双中不能有割点,所以这两部分肯定不在同一个点双中,所以割点至少存在于两个点双中。

点双联通的的定义是:仅仅对于该图而言,其中不包含割点,那么称这个图是点双连通的,也就是说,可以包含原图中的割点。

3、 任意一个不是割点的点都只存在于一个点双中。

若一个点存在于两个点双中,那么将它除去了以后这两个点双就不再连通,所以这个点是割点,由此得证。

这个性质应该算是性质二的一个相反的性质

点双同样也为 tarjan 算法来求的

int tarjan(int u,int fa){//点双模板,大致介绍一下
// u是当前点,fa是父亲点; 
    int lowu = dfn[u] = ++now;
    int son = 0;
    for(int i = head[u];i;i = ee[i].ne){
        int v = ee[i].to;
        Node e = (Node){u,v};
        if(!dfn[v]){
            son++;
            st.push(e);
            int lowv = tarjan(v,u);
            lowu = min(lowu,lowv);
            if(lowv>=dfn[u]){
                cut[u] = 1;//是割点
                iscut = iscut+1;//割点数加1
                ma[iscut].clear();
                for(;;){
                    Node x = st.top();
                    st.pop();
                    if(wyy[x.u]!=iscut){
                        ma[iscut].push_back(x.u);
                        wyy[x.u]=iscut;
                    }
                    if(wyy[x.v]!=iscut){
                        ma[iscut].push_back(x.v);
                        wyy[x.v]=iscut;
                    }
                    if(x.u==u&&x.v==v) break;
                }
            }
        }else{
            if(dfn[v]<dfn[u]&&v!=fa){
                st.push(e);
                lowu=min(lowu,dfn[v]);
            }
        }
    }
    if(fa<0&&son==1) cut[u] = 0;////如果u是根节点,u有>=2个孩子;u是割点 
    return lowu;
}

嗯,点双差不多就这样了

习题:[P3225 HNOI2012]矿场搭建(恶补的原因)

综上所述,我是蒟蒻

2022-10-27 11:12:59

posted @ 2022-10-27 11:12  cztq  阅读(28)  评论(0编辑  收藏  举报