割点与割边
割点
算法流程
#include <bits/stdc++.h>
using namespace std;
int n,m;
vector <int> e[20001];
int dfn[20001],low[20001],timer;
bool gd[20001];
int ans;
void tarjan(int x,int fa)
{
dfn[x] = low[x] = ++timer;
int child = 0;
for (int i = 0; i < e[x].size(); i++)
{
if(!dfn[e[x][i]])
{
tarjan(e[x][i],fa);
low[x] = min(low[x],low[e[x][i]]);
if(low[e[x][i]] >= dfn[x] && x != fa)//不是根节点
{
gd[x] = true;
}
if(x == fa)//根节点
{
child++;
}
}
low[x] = min(low[x],dfn[e[x][i]]);
}
if(child >= 2)
{
gd[x] = true;
}
}
int main()
{
cin >> n >> m;
int a,b;
for (int i = 1; i <= m; i++)
{
cin >> a >> b;
e[a].push_back(b),e[b].push_back(a);
}
for (int i = 1; i <= n; i++)
{
if(!dfn[i])
{
tarjan(i,i);
}
}
for (int i = 1; i <= n; i++)
{
if(gd[i])ans++;
}
cout << ans << endl;
for (int i = 1; i <= n; i++)
{
if(gd[i])cout << i << " ";
}
return 0;
}
类似于tarjan求scc
判定
每次选取一个点作为根节点,判断割点分为根节点与非根节点的判断:
根节点:只需要独立的子树(当遍历其它子树时,不会遍历到当前子树,即不连通,就相当于是独立了)大于两个即可判定为割点
非根节点:其下的子节点存在一个的low值大于等于当前节点dfn值,即最多遍历到当前节点,不能连到祖先或者旁系了,也就相当于是局部独立了
fa表示根节点
与tarjan区别:
tarjan中的min值(low,dfn)dfn貌似是可以改为low的(待定,本人未证明)
而这里不行,这里是靠dfn来判断属于哪一颗子树的,因为算法实际上是假设了把当前节点删去后,判断连通性,若直接用low的话,相当于时无视了假设,边与点仍然存在,并且帮助转移,所以不行
这里else要不要无所谓。
割边
算法流程
struct node {int to,nxt; } e[M * 2];
int hd[N],nE = 1;
void add(int u,int v)
{
e[++nE] = (node){ v,hd[u]};
hd[u] = nE;
}
int dfn[20001],low[20001],timer;
int ans;
void tarjan(int x,int fa)
{
dfn[x] = low[x] = ++timer;
for (int i = hd[x]; i; i = e[i].nxt)
{
int v = e[i].to;
if(!dfn[v])
{
tarjan(v,i ^ 1);//传反向边
low[x] = min(low[x],low[v]);
if(low[v] > dfn[x])
{
ans++;
}
}
else if(fa != i) low[x] = min(low[x],dfn[v]);
}
}
int main1()
{
int a,b;
for (int i = 1; i <= m; i++)
{
a = data[i][0],b = data[i][1];
add(a,b),add(b,a);
}
for (int i = 1; i <= n; i++)
{
if(!dfn[i])
{
tarjan(i,i);
}
}
printf("%d ",ans);
return 0;
}
(尚未验证,大概是对的,慎用)
思想差不多,但简化了很多,最不同的地方在于 >= 变成 >,意为连父节点都到不了了
与割点有两点不同:
① 对于节点 i 连向子节点 j 的边,如果 j 所能到达的最小时间戳值都大于(不能等于,这是不同之处) i 的时间戳时,才判定此边为割边,不能等于意味着不通过这条边,子节点连 i 号点本身也到不了,这样,如果此边被割去,j 号点也就被割开了。
② 在更新能到达的最小时间戳时,需要跳过反向边。
浙公网安备 33010602011771号