tarjan思想延伸应用-割点割边的数量求解

目录

割点:

割边:


Tarjan求强连通分量的个数,前面介绍过,在这里稍微一略过。在有向图中,寻找一个点所能到达的最小的时间戳(回溯的被遍历个数最小的编号)。对于一个结点u,那么low[u]肯定是本身++time和所有子节点low[v]的最小的一个(v可到达的,u都可以到达),更友好的是,点可以多次经过(存在重边或者两点形成环等),所以可得核心代码:

       for(int i = head[u];i != -1;i = edge[i].next){

              int v = edge[i].to;

              if(!dfn[v]){

                     tarjan(v);

                     low[u] = min(low[v],low[u]);

              }

              else if(vis[v])   low[u] = min(low[v],low[u]);

       }

vis代表仍在栈中,也就是从栈低元素一路走过来,所以u如果可以到已访问的v(祖先节点),则必成环,必在一个强连通分量中,所以用low[v]早更新也一样。

 

割点:

       割点:删除该点后,一个图分成了两个连通分支。意味着该点所有的子节点最多可以回溯到该割点本身,不可能不经过父节点的情况下回溯更早。

       因为不可以经过父节点,所以修改的第一条:

else if(vis[v])   low[u] = min(low[v],low[u]);中的low[u] = min(low[v],low[u]);

       比如v把该图分成左右两部分,v连接起来。u在右侧,如果v的low是比v要小的话,这时候u就更新为了low[v],u回溯到了左侧,但是经过了v,所以需要改为:

low[u] = min(low[u],dfn[v]);

对于根节点,如果可以遍历一遍全部完成就肯定不是割点,否则必为割点,记录下即可。

代码:

#include<algorithm>

#include<iostream>

#include<cstring>

#include<string>

#include<cstdlib>

#include<map>

#include<cmath>

#include<vector>

#include<cstdio>



using namespace std;

typedef long long ll;

const int maxn = 2e5+50;

struct ed{

         int to;

         int next;

}edge[maxn];

int head[maxn];

int dfn[maxn],low[maxn];

bool vis[maxn];

int tme = 0,tot = 0;

void add(int u,int v){

         edge[tot].to = v;

         edge[tot].next = head[u];

         head[u] = tot++;

}

void tarjan(int u,int root,int fa){

         int children = 0;

         dfn[u] = low[u] = ++tme;

         for(int i = head[u];i != -1; i = edge[i].next){

                  int v = edge[i].to;

                  if(!dfn[v]){

                          tarjan(v,root,u);

                          if(u == root)     children++;//记录回溯到根节点次数

                          low[u] = min(low[u],low[v]);

                          if(children>1&&u == root)      vis[u] = true;

                          if(u!=root&&low[v] >= dfn[u])        vis[u] = true;

                  }

                  else if(fa!=v)  low[u] = min(low[u],dfn[v]);

                  //与求强连通分量区别在于low[u]=min(low[u],low[v])在这里会出错。

                  //因为强连通分量v一定是在栈中,也就是肯定是在一个强连通分量中

                  //早更新晚更新都无所谓

                  //并且强连通分量是允许经过父节点!!! 这个不允许。

         }

}

int main(){

         int n,m,x,y;

         cin >> n >> m;

         for(int i = 1;i <= n;i++)    head[i] = -1;

         for(int i = 1;i <= m;i++){

                  cin >> x >> y;

                  add(x,y);

                  add(y,x);

         }

         for(int i = 1;i <= n;i++)

                  if(!dfn[i])         tarjan(i,i,i);

         vector<int>ans;

         for(int i = 1;i <= n;i++)

                  if(vis[i])   ans.push_back(i);

         cout<<ans.size()<<endl;

         //割点个数

         for(int i = 0;i < ans.size();i++) cout<<ans[i]<<' ';

         //割点编号

         cout<<endl;             

         return 0;

}

割边:

         割边:删除该边后,一个图变为两个连通分支。

割点是还能不经过父节点还存在回溯到父节点的情况。割边的话是根本没法回到父节点。相比割点,只需改下:if(low[v] > dfn[u])即可。

         细节区别:根节点无需特判,不可用结点标记计数,因为一个结点可能连接多个割边。

代码:

#include<algorithm>

#include<iostream>

#include<cstring>

#include<string>

#include<cstdlib>

#include<map>

#include<cmath>

#include<vector>

#include<cstdio>



using namespace std;

typedef long long ll;

const int maxn = 4e6+50;

struct ed{

         int to;

         int next;

}edge[maxn];

int ans = 0;

int head[maxn];

int dfn[maxn],low[maxn];

bool vis[maxn];

int tme = 0,tot = 0;

void add(int u,int v){

         edge[tot].to = v;

         edge[tot].next = head[u];

         head[u] = tot++;

}

void tarjan(int u,int fa){

         //割点需要记录根,特判根。割边需要记住父节点,无向图转化为双向图,这时候孩子一旦

         //遍历了父节点,也跟新了父节点的dfn,就不存在>的情况了

         //割点记录fa也不会错,更好,因为更新的应该是祖先节点,即不经过父节点

         dfn[u] = low[u] = ++tme;

         for(int i = head[u];i != -1; i = edge[i].next){

                  int v = edge[i].to;

                  if(!dfn[v]){

                          tarjan(v,u);

                          low[u] = min(low[u],low[v]);

                          //不能像割点一样进行标记

                          //割点标记是否点是因为求割点数目

                          //一个点可能连多个割边,所以需要直接++,或者存点对。

                          if(low[v] > dfn[u])   ans++;

                  }

                  else if(fa != v)  low[u] = min(low[u],dfn[v]);

         }

}

int main(){

         int n,m,x,y;

         cin >> n >> m;

         for(int i = 1;i <= n;i++)    head[i] = -1;

         for(int i = 1;i <= m;i++){

                  cin >> x >> y;

                  add(x,y);

                  add(y,x);

         }

         for(int i = 1;i <= n;i++)

                  if(!dfn[i])         tarjan(i,i);

         cout<<ans<<endl;

         return 0;

}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值