割点
割点:
当把强联通分量里面的的一个点删掉以后发现强联通分量的数目增加了,那么就说这个点是割点(割顶)
看到这个要求的时候,第一反应是枚举每一个强联通分量然后判断去掉这个点后强联通分量然后就会发现这个复杂度是上天的强联通分量的时间复杂度是O(n+m)的 如果按照这种想法的话,大约是O(((n+m)^2))瞎算的不知道对不对,反正差不是这个级数的时间复杂度然后tarjan这个人他就又来了,根据强联通分量的算法,变式出这样一种求割点的方法
主函数与强联通分量差不多主要区别在tarjan函数
- 首先选定一个根节点,从该根节点开始遍历整个图(使用DFS)。
- 对于根节点,判断是不是割点很简单——计算其子树数量,如果有2棵即以上的子树,就是割点。因为如果去掉这个点,这两棵子树就不能互相到达。
- 对于非根节点,判断是不是割点就有些麻烦了。我们维护两个数组dfn[]和low[],dfn[u]表示顶点u第几个被(首次)访问,low[u]表示顶点u及其子树中的点,通过非父子边(回边),能够回溯到的最早的点(dfn最小)的dfn值(但不能通过连接u与其父节点的边)。对于边(u, v),如果low[v]>=dfn[u],此时u就是割点。
- 但这里也出现一个问题:怎么计算low[u]。
- 假设当前顶点为u,则默认low[u]=dfn[u],即最早只能回溯到自身。
- 有一条边(u, v),如果v未访问过,继续DFS,DFS完之后,low[u]=min(low[u], low[v]);
- 如果v访问过(且u不是v的父亲),就不需要继续DFS了,一定有dfn[v]<dfn[u],low[u]=min(low[u], dfn[v])。
来源
板子是自己的板子
注意 这里第二个low[u] 必须是
low[u] = min(low[u],dfn[to]);
要不然就是一片红,具体原理上面的链接里也有解释
void targan(int u, int fa){
int child_num = 0; // u 为根节点子树的数目
dfn[u] = low[u] = ++cnt;
for(int i = head[u] ; i ;i = e[i].next){
int to = e[i].to;
if(!dfn[to]){
targan(to,fa);
low[u] = min(low[u],low[to]);
if(low[to] >= dfn[u]&&u != fa ) cut[u] = 1;
if(u == fa) child_num++;
}
low[u] = min(low[u],dfn[to]);
}
if (child_num >= 2 && u == fa ) cut[u] = 1;
}
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const int N = 2e5+10;
int low[N],dfn[N],cut[N];
struct node{
int from , to, next;
}e[N << 1];
int head[N] , nume;
void add_edge(int from, int to){
e[++nume].from = from;
e[nume].to = to;
e[nume].next = head[from];
head[from] = nume;
}
int read(){
int x=0;bool f=0;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=!f;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return f?-x:x;
}
int cnt = 0,ans = 0;
void tir(int u, int fa){
int child_num = 0;
dfn[u] = low[u] = ++cnt;
for(int i = head[u] ; i ;i = e[i].next){
int to = e[i].to;
if(!dfn[to]){
tir(to,fa);
low[u] = min(low[u],low[to]);
if(low[to] >= dfn[u]&&u != fa ) cut[u] = 1;
if(u == fa) child_num++;
}
low[u] = min(low[u],dfn[to]);
}
if (child_num >= 2 && u == fa ) cut[u] = 1;
}
int main(){
int n = read() , m = read();
int u , v;
for(int i = 1 ,u,v; i <= m ;i++){
u = read() , v = read();
add_edge(u,v),add_edge(v,u);
}
for(int i = 1 ; i <= n ;i++){
if(!dfn[i]) tir(i,i);
}
for(int i = 1 ; i <= n ;i++) if(cut[i]) ans++;
cout<<ans<<"
";
for(int i = 1 ; i <= n ;i++){
if(cut[i]) printf("%d ",i);
}
return 0;
}
桥(割边)
emm这个原理与割点差不多,就是把点换成边而且不用关心根节点
- 如果low[to] > dfn[u] to不能通过另一条路径回到u之前所以u--->to这条边是桥
Code:
口胡的不一定对
void tarjan(int u,int fa){
fath[u] = fa;
low[u] = dfn[u] = ++cnt;
for(int i = head[u] ; i ; i = e[i].next){
int to = e[i].to;
if(!dfn[to]){
targan(to,u),low[u] = min(low[u],low[to]);
if(low[to] > dfn[u]){
cutbridge[to] = 1;
cut_num++;
}
}else if(dfn[to] < dfn[u] && to != fa){
low[u] = min(low[u],dfn[to]);
}
}
}