点双连通分量和割点的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
梦与现实间挣扎着,所求为何
你可以借走我的文章,但你借不走我的智慧 虽然我是傻逼本文来自博客园,作者:cztq,转载请注明原文链接:https://www.cnblogs.com/cztq/p/16831371.html