连通分量、割点割边、广义圆方树总结

【强连通分量算法】

Kosaraju 算法/Tarjan 算法

【强连通分量实现】

//Kosaraju
// g 是原图,g2 是反图

void dfs1(int u) {
  vis[u] = true;
  for (int v : g[u])
    if (!vis[v]) dfs1(v);
  s.push_back(u);
}

void dfs2(int u) {
  color[u] = sccCnt;
  for (int v : g2[u])
    if (!color[v]) dfs2(v);
}

void kosaraju() {
  sccCnt = 0;
  for (int i = 1; i <= n; ++i)
    if (!vis[i]) dfs1(i);
  for (int i = n; i >= 1; --i)
    if (!color[s[i]]) {
      ++sccCnt;
      dfs2(s[i]);
    }
}
// tarjan
int dfn[N], low[N], dfncnt, s[N], in_stack[N], tp;
int scc[N], sc;  // 结点 i 所在 SCC 的编号
int sz[N];       // 强连通 i 的大小
void tarjan(int u) {
  low[u] = dfn[u] = ++dfncnt, s[++tp] = u, in_stack[u] = 1;
  for (int i = h[u]; i; i = e[i].nex) {
    const int &v = e[i].t;
    if (!dfn[v]) {
      tarjan(v);
      low[u] = min(low[u], low[v]);
    } else if (in_stack[v]) {
      low[u] = min(low[u], dfn[v]);
    }
  }
  if (dfn[u] == low[u]) {
    ++sc;
    while (s[tp] != u) {
      scc[s[tp]] = sc;
      sz[sc]++;
      in_stack[s[tp]] = 0;
      --tp;
    }
    scc[s[tp]] = sc;
    sz[sc]++;
    in_stack[s[tp]] = 0;
    --tp;
  }
}

【割点】

1.定义:

        删除某个点后,这个图就不再联通,称这个点为割点

2.求法

        显然,如果根节点有多于两个儿子就一定是割点,其他点如果至少存在一个儿子的dfn[x]<=low[son],那么这个点就是割点

【割点实现】

void tarjan(int u,int fa)
{
	low[u]=dfn[u]=++times;
	int child=0;
	for(int i=head[u];i;i=e[i].nxt)
	{
		int to=e[i].to;
		if(!dfn[to])
		{
			tarjan(to,fa);
			low[u]=min(low[to],low[u]);
			if(low[to]>=dfn[u] && u!=fa && !cut[u]) cut[u]=1,cnt++;
			if(u==fa) child++;
		}	
		else low[u]=min(low[u],dfn[to]);
	}	
	if(child>=2 && u==fa)
	{
		cut[u]=1;
		cnt++;
	}
}

【割边】

1.定义:

        在无向图中删去这条边,图不再联通,就成这条边为割边

2.求法

        low[v]>num[u]即可表示u-v之间的边为割边

【割边实现】

void tarjan(int u, int fa) {
  father[u] = fa;
  low[u] = dfn[u] = ++dfs_time;
  for (int i = 0; i < G[u].size(); i++) {
    int v = G[u][i];
    if (!dfn[v]) {
      tarjan(v, u);
      low[u] = min(low[u], low[v]);
      if (low[v] > dfn[u]) {
        isbridge[v] = true;
        ++cnt_bridge;
      }
    } else if (dfn[v] < dfn[u] && v != fa) {
      low[u] = min(low[u], dfn[v]);
    }
  }
}

【双联通分量】

在一张连通的无向图中,对于两个点u和v,如果无论删去哪条边(只能删去一条)都不能使它们不连通,我们就说u和v边双连通

在一张连通的无向图中,对于两个点u和v,如果无论删去哪个点(只能删去一个,且不能删u和v自己)都不能使它们不连通,我们就说u和v点双连通

其中,边双有传递性,点双没有传递性,下图为反例

在一个无向连通图中,如果没有割点,那么它是点双连通图。一个无向图的极大点双连通子图就是点双连通分量

在一个无向连通图中,如果没有割边,那么它是边双连通图。一个无向图的极大边双连通子图就是边双连通分量

【双联通分量实现】

//点双
void tarjan(int x,int in_edge){
        low[x]=dfn[x]=++cnt;sta[++t]=x;
        for(int i=head[x];i+1;i=a[i].next){
            int y=a[i].y;
            if(!dfn[y]){
                tarjan(y,i);
                low[x]=min(low[x],low[y]);
                if(low[y]>=dfn[x]){
                    int u;
                    dcc++;size=0;
                    vdcc.clear();
                    do{
                        u=sta[t--];
                        block[u]=dcc;
                        size++;
                        vdcc.push_back(u);
                    }while(u!=y);
                    block[x]=dcc;
                    vdcc.push_back(x);
                    size++;
                }
            }
            else low[x]=min(low[x],dfn[y]);
        }
    }
int low[maxn],dfn[maxn],cnt=0;
    bool bridge[maxm<<1];
    void tarjan(int x,int in_edge){
        dfn[x]=low[x]=++cnt;
        for(int i=head[x];i+1;i=a[i].next){
            int y=a[i].y;
            if(!dfn[y]){
                tarjan(y,i);
                low[x]=min(low[x],low[y]);
                if(low[y]>low[x])bridge[i]=bridge[i^1]=true;
            }
            else if(i!=(in_edge^1))
                low[x]=min(low[x],dfn[y]);
        }
    }
    int block[maxn],dcc=0;
    void dfs(int x,int color){
        block[x]=color;
        for(int i=head[x];i+1;i=a[i].next){
            int y=a[i].y;
            if(bridge[i])continue;
            if(!block[y])dfs(y,color);
        }
    }
    void get_edccs(){
        for(int i=1;i<=n;i++)
            if(!block[i])
                dfs(i,++dcc);
    }

【题型总结】

1.tarjan缩点

        1.度数相关

        P2341 [USACO03FALL][HAOI2006]受欢迎的牛 G

        缩点后,如果出度为0的点大于1个,那么不存在受欢迎的牛,否则那个出度为0的点集为受欢迎的牛的集合  代码

       P2002消息扩散 

        缩点后求入度为0的点个数
代码

       [USACO5.3]校园网Network of Schools 

        分别为入度为0个数和max{入度为0个数,出度为0个数},注意特判初始联通的情况 代码

       P1262 间谍网络

        缩点后,求所有度数为0集合中最小值的和,如果跑完tarjan仍有点未经过,那么一定无法全部收买     代码

        2.DAG上dp(最长路)

        [ZJOI2007]最大半连通子图 

        题目转化为,缩点后求DAG上的最长链及方案数,dp即可 代码

        [APIO2009]抢掠计划 

        因为可以重复经过边/点,所以强连通分量内的点一定都可以走到,所以我们的问题就转换为了缩点后求最长路 代码

        [HNOI2006]潘多拉的宝盒 

        这道题目的主要在于判断两个咒语机是否有升级关系,其实用dfs跑一边,两个机器同时跑,保证当前时刻的字符串相同,如果此时A有输出,B无输出,那么B就不是A的升级,在建图后,直接缩点最长路  代码

        [POI2012]FES-Festival 

        首先建立差分约束系统,然后答案转换为最长路+1,floyd会超时,考虑负环一定不可行,全是1的正环也不可行,但由于+1-1是正反挨着建的图,所以直接判断有没有负环即可判读是否有解。一个强连通分量中的相对关系是确定的,贡献为最长路长度,强连通分量之间的边一定是0的边,所以它们的差值随意,故最终答案为所有强连通分量的最长路+强连通分量个数  代码

        3.特殊建图

        [国家集训队]稳定婚姻 

        夫妻之间:girl→boy 情人之间:boy→girl,此时如果对夫妻在同一个强连通分量中就危险了

        代码

       

2.割点

        poj2117 Electricity

        割点模板 代码

        [ZJOI2004]嗅探器 

        我们要断开一个点,使得A.B分开,那么这个点一定是割点,同时我们要判断这个割点是否把A和B割到了两个部分,也就是dfn[割点]在dfn[A]和dfn[B]之间  代码

        P3225 [HNOI2012]矿场搭建  

        求出每个点双中的割点数量,没有割点就需要建两个,有一个割点就需要建一个,有两个割点就不需要建了  代码

 

3.割边

        HDU 4738 Caocao‘s Bridges

        求所有割边中最小值,细节较多比如重边不能是割边,而且开始就不联通答案为0,边权为0也要派一个人去 代码

4.广义圆方树

        对于无向图上的简单路径问题,我们可以考虑用圆方树解决

        我们找到所有点双,对于每个点双连通分量,新建立一个方节点,将其向点双的每个圆点连边,这样就得到了一棵树

        性质:1.只有圆圆边和圆方边 2.一个方点对应一个点双 3.方点的度数为点双大小

        [APIO2018] Duathlon 铁人两项 

        两圆点在圆方树上的路径,与路径上经过的方点相邻的圆点的集合,就等于原图中两点简单路径上的点集。回到题目,考虑固定s和f,求合法的c的数量,显然有合法c的数量等于s和f之间简单路径的并集的点数减2(去掉s和f本身)。那么,对原图建出圆方树后,两点之间简单路径的点数,就和它们在圆方树上路径经过的方点(点双)和圆点的个数有关。

        接下来是圆方树的一个常用技巧:路径统计时,点赋上合适的权值。本题中,每个方点的权值为对应点双的大小,而每个圆点权值为 -1。那么这样赋权后则有两圆点间圆方树上路径点权和,恰好等于原图中简单路径并集大小减2。

        至此,问题转化为求任意两圆点间路径权值和。

        这里又有一个常见tips:考虑转化为点的贡献,即权值乘以经过它的路径条数,利用树形(换根)dp求解即可 代码

        

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值