割点 —— Tarjan 算法

由于对于这一块掌握的十分不好,所以在昨天做题的过程中一直困扰着我,好不容易搞懂了,写个小总结吧 qwq~

割点

概念

无向连通图中,如果将其中一个点以及所有连接该点的边去掉,图就不再连通,那么这个点就叫做割点 。

比如我们现在有一个图:

如果我们将 4 号节点及它的所有边全部删去,那么这个图就变得不再联通,所以 4 号点是一个割点:

同理,5 号节点也是一个割点:

怎么求割点

我们可以用 Tarjan 算法去求割点;

有两个关键的数组:

dfn [ i ] :表示编号为 i 的点在 dfs 过程中是第几个被遍历到的(时间戳);

low [ i ]:表示编号为 i 的点的子树中的节点所能到达的最小时间戳是多少;

这两个数组不需要再多多介绍了吧?想必大家在学 Tarjan 算法的时候都熟练掌握了;

重点说一下怎么求割点:

首先,割点都是定义在无向图中的,所以我们可以任选一个点为根(一般是 1 号节点)开始 Tarjan 算法;

考虑什么样的点才可能是割点呢?

我们上面提到了 4 号节点是一个割点,那是因为删除 4 号节点及其所连的边后,1 2 3 和 5 6 就不连通了,也就是说,除了 4 号结点所连的边外,5 6 号结点没有其余的边连向 1 2 3,也就是说没有返祖边;

既然如此,4 号点之下的点所能到达的最小时间戳一定不超过 dfn [ 4 ] ,不然就到了 4 号点之上了;

那么我们就得到了一个判断一个点是否是割点的条件:

如果一个点 u 满足 low [ u ] >= dfn [ u ],那么点 u 就是一个割点;

但是……

根节点的 dfn 和 low 值初始值都为 1,之后再怎么更新 low [ root ] 始终都是 1,那这么一搞的话根结点始终是割点?

显然不是啊!

对于根结点,我们要另想办法qwq~

看一棵十分丑陋的树:

发现如果将 1 结点及其所有的边删掉,那么 2 5 4 和 3 6 7 将不连通,说明此时 1 是一个割点;

发现此时根节点有两棵互不相连的子树;

所以我们可以记录根节点有几棵互不相连的子树,如果大于 1 棵的话,根节点就是一个割点;

如果有几棵子树是相连的,那么会在 Tarjan 的时候将它们算成一棵子树:

P3388 【模板】割点(割顶)  的代码:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
using namespace std;
int read()
{
    char ch=getchar();
    int a=0,x=1;
    while(ch<‘0‘||ch>‘9‘)
    {
        if(ch==‘-‘) x=-x;
        ch=getchar();
    }
    while(ch>=‘0‘&&ch<=‘9‘)
    {
        a=(a<<1)+(a<<3)+(ch-‘0‘);
        ch=getchar();
    }
    return a*x;
}
int n,m,tim,top,edge_sum,scc_sum,tot;
int head[1000001],dfn[1000001],low[1000001],vis[1000001],st[1000001],u[1000001],v[1000001],ans[1000001];
queue<int> q;
struct node
{
    int from,to,next;
}a[1000001];
void add(int from,int to)
{
    edge_sum++;
    a[edge_sum].next=head[from];
    a[edge_sum].from=from;
    a[edge_sum].to=to;
    head[from]=edge_sum;
}
void tarjan(int u,int root)
{
    int child=0;                                 //记录根节点有几棵互不相连的子树
    dfn[u]=low[u]=++tim;
    for(int i=head[u];i;i=a[i].next)
    {
        int v=a[i].to;
        if(!dfn[v])
        {
            tarjan(v,root);
            low[u]=min(low[u],low[v]);
            if(low[v]>=dfn[u])                   //如果
            {
                child++;                         //记录根节点有多少个互不相连的子树
                if(u!=root||child>1) ans[u]=1;   //如果不是根节点,那么直接就是割点了;如果是根节点,并且互不相连的子树个数超过1棵,则也是割点
            }
        }
        else low[u]=min(low[u],dfn[v]);
    }
}
int main()
{
    n=read();m=read();
    for(int i=1;i<=m;i++)
    {
        u[i]=read();
        v[i]=read();
        add(u[i],v[i]);                          //建双向边
        add(v[i],u[i]);
    }
    for(int i=1;i<=n;i++)                        //有可能图不是联通的,要多进行几次Tarjan
    {
        if(!dfn[i]) tarjan(i,i);
    }
    for(int i=1;i<=n;i++)                        //求割点个数
    {
        if(ans[i]) tot++;
    }
    printf("%d\n",tot);
    for(int i=1;i<=n;i++)
    {
        if(ans[i]) printf("%d ",i);              //输出每个割点
    }
    return 0;
}

再看个例题:

题目大意

给定一张无向图,求每个点被封锁之后有多少个有序点对 ( x , y ) ( x != y ,1 <= x , y <= n ) 满足 x 无法到达 y;

题解

很显然这是一个让你求割点的问题,所以我们根据被封锁的这个点 u 是不是割点来分两种情况讨论:

1. 如果 u 不是割点:

即把 u 和它有关的所有边都去除后图依然联通,那么这个图只有 u  是独立在外面的,由于求的是有序点对,所以除了 u 以外的 n-1 个点作为一个大的连通图对 u 加边,即为 2 ∗ ( n − 1 ) 对;

2. 如果 u 是割点:

假如 u 是割点,那么会把图分为 a 个连通块以及 u 本身,由于 Tarjan 在求割点的过程中是一棵搜索树往下遍历,所以除了它和它的子树外,还会有其他剩余点共同构成另一个连通块;

删掉 u 后肯定有一些子树不与大联通块联通了(满足条件 low [ v ] >= dfn [ u ]),设这些子树的根节点分别为 1,2,3,……,a,这些子树所包含的结点数为 tot = size [ 1 ] + size [ 2 ] + size [ 3 ] + …… + size [ a ];

那么点 u 的最后答案就是:

ans[u] = size[1] * (n-size[1]-1) + size[2] * (n-size[2]-1) + …… + size[a] * (n-size[a]-1) + tot * (n-tot-1) + 2*(n-1);

解释一下式子怎么来的:

首先在求割点的过程中,每次遇到 low [ v ] >= dfn [ u ],就要把 u 的答案加上 size [ v ] * ( n - size [ v ] - 1 ),这些是子树不能到达外面结点(没有 u)的贡献;

考虑完子树对外的贡献后,同样外面的结点也不能到达这些子树内的结点;

子树内的所有点是 tot,那么外面的点就是 n - tot - 1,那么外面结点对子树内的贡献再加上就是 tot * ( n - tot - 1 ) ;

然后再考虑点 u 的单独的贡献,显然它无法到达任何点,同样任何点也无法到达它,那么答案再加上 2*(n-1);

最后注意开 long long 哦~

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<vector>
using namespace std;
long long read()
{
    char ch=getchar();
    long long a=0,x=1;
    while(ch<‘0‘||ch>‘9‘)
    {
        if(ch==‘-‘) x=-x;
        ch=getchar();
    }
    while(ch>=‘0‘&&ch<=‘9‘)
    {
        a=(a<<1)+(a<<3)+(ch-‘0‘);
        ch=getchar();
    }
    return a*x;
}
const int N=1e6;
long long n,m,top,tim,num,root,edge_sum;
long long head[N],yes[N],dfn[N],low[N],st[N],vis[N],size[N],u[N],v[N],ans[N],son[N];
struct node
{
    int next,to,from;
}a[N];
void add(int from,int to)
{
    edge_sum++;
    a[edge_sum].from=from;
    a[edge_sum].to=to;
    a[edge_sum].next=head[from];
    head[from]=edge_sum;
}

void tarjan(int u)                                       //Tarjan求割点
{
    dfn[u]=low[u]=++tim;
    size[u]=1;                                           //求以点u为根的树的大小
    long long child=0,tot=0;
    for(int i=head[u];i;i=a[i].next)
    {
        int v=a[i].to;
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
            size[u]+=size[v];
            if(low[v]>=dfn[u])
            {
                child++;                                 //子树棵数++
                ans[u]+=size[v]*(n-size[v]-1);           //统计这些子树内的结点对外结点的贡献
                tot+=size[v];                            //统计这些子树内的结点总数
                if(u!=root||child>1) yes[u]=1;           //如果u不是根节点,那么直接就是割点了;如果是根节点,并且有超过1棵子树的话,也是割点
            }
        }
        else low[u]=min(low[u],dfn[v]);
    }
    if(yes[u]) ans[u]+=tot*(n-tot-1)+2*(n-1);            //tot*(n-tot-1)是外结点对子树内结点的贡献,2*(n-1)是点u对所有点的贡献
}
int main()
{
    n=read();m=read();
    for(int i=1;i<=m;i++)
    {
        u[i]=read();v[i]=read();
        add(u[i],v[i]);
        add(v[i],u[i]);
    }
    root=1;
    tarjan(1);                                           //题目的信息说明了这个图原本是联通的,所以我们求一次Tarjan就好了
    for(int i=1;i<=n;i++) if(!yes[i]) ans[i]=2*(n-1);    //不是割点的话,答案就是2*(n-1)
    for(int i=1;i<=n;i++) printf("%lld\n",ans[i]);
    return 0;
}

希望CSP不要考到这一块的内容啊qwq

最后祝大家 CSP rp ++

原文地址:https://www.cnblogs.com/xcg123/p/11832990.html

时间: 2024-11-06 09:59:01

割点 —— Tarjan 算法的相关文章

tarjan算法--求无向图的割点和桥

一.基本概念 1.桥:是存在于无向图中的这样的一条边,如果去掉这一条边,那么整张无向图会分为两部分,这样的一条边称为桥无向连通图中,如果删除某边后,图变成不连通,则称该边为桥. 2.割点:无向连通图中,如果删除某点后,图变成不连通,则称该点为割点. 二:tarjan算法在求桥和割点中的应用 1.割点:1)当前节点为树根的时候,条件是“要有多余一棵子树”(如果这有一颗子树,去掉这个点也没有影响,如果有两颗子树,去掉这点,两颗子树就不连通了.) 2)当前节点U不是树根的时候,条件是“low[v]>=

割点(Tarjan算法)

本文可转载,转载请注明出处:www.cnblogs.com/collectionne/p/6847240.html .本文未完,如果不在博客园(cnblogs)发现此文章,请访问以上链接查看最新文章. 前言:之前翻译过一篇英文的关于割点的文章(英文原文.翻译),但是自己还有一些不明白的地方,这里就再次整理了一下.有兴趣可以点我给的两个链接. 割点的概念 在无向连通图中,如果将其中一个点以及所有连接该点的边去掉,图就不再连通,那么这个点就叫做割点(cut vertex / articulation

Tarjan算法:求解图的割点与桥(割边)

简介: 割边和割点的定义仅限于无向图中.我们可以通过定义以蛮力方式求解出无向图的所有割点和割边,但这样的求解方式效率低.Tarjan提出了一种快速求解的方式,通过一次DFS就求解出图中所有的割点和割边. 欢迎探讨,如有错误敬请指正 如需转载,请注明出处 http://www.cnblogs.com/nullzx/ 1. 割点与桥(割边)的定义 在无向图中才有割边和割点的定义 割点:无向连通图中,去掉一个顶点及和它相邻的所有边,图中的连通分量数增加,则该顶点称为割点. 桥(割边):无向联通图中,去

『Tarjan算法 无向图的割点与割边』

无向图的割点与割边 定义:给定无相连通图\(G=(V,E)\) 若对于\(x \in V\),从图中删去节点\(x\)以及所有与\(x\)关联的边后,\(G\)分裂为两个或以上不连通的子图,则称\(x\)为\(G\)的割点. 若对于\(e \in E\),从图中删去边\(e\)之后,\(G\)分裂为两个不连通的子图,则称\(e\)为\(G\)的割边. 对于很多图上问题来说,这两个概念是很重要的.我们将探究如何求解无向图的割点与割边. 预备知识 时间戳 图在深度优先遍历的过程中,按照每一个节点第一

tarjan算法(强连通分量 + 强连通分量缩点 + 桥 + 割点 + LCA)

这篇文章是从网络上总结各方经验 以及 自己找的一些例题的算法模板,主要是用于自己的日后的模板总结以后防失忆常看看的, 写的也是自己能看懂即可. tarjan算法的功能很强大, 可以用来求解强连通分量,缩点,桥,割点,LCA等,日后写到相应的模板题我就会放上来. 1.强连通分量(分量中是任意两点间都可以互相到达) 按照深度优先遍历的方式遍历这张图. 遍历当前节点所出的所有边.在遍历过程中: ( 1 ) 如果当前边的终点还没有访问过,访问. 回溯回来之后比较当前节点的low值和终点的low值.将较小

Tarjan算法与割点割边

目录 Tarjan算法与无向图的连通性 1:基础概念 2:Tarjan判断割点 3:Tarjan判断割边 Tarjan算法与无向图的连通性 1:基础概念 在说Tarjan算法求解无向图的连通性之前,先来说几个概念: <1. 时间戳:在图的深度优先遍历中,按照每一个结点第一次被访问到的时间顺序,依次给予N个结点1~N的整数边集,该标记就被计位"时间戳",计做 \(dfn[x]\). <2. 搜索树:任选一个结点深度优先遍历,每个点只访问一次.产生递归的边构成的树为搜索树. &

【转】BYV--有向图强连通分量的Tarjan算法

转自beyond the void 的博客: https://www.byvoid.com/zhs/blog/scc-tarjan 注:红色为标注部分 [有向图强连通分量] 在有向图G中,如果两个顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components). 下图中,子图{1,2,3,4}为一个强连通分量,因为顶

POJ1523(求连用分量数目,tarjan算法原理理解)

SPF Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 7406   Accepted: 3363 Description Consider the two networks shown below. Assuming that data moves around these networks only between directly connected nodes on a peer-to-peer basis, a

TarJan 算法求解有向连通图强连通分量

[有向图强连通分量] 在有向图G中,如果两个 顶点间至少存在一条路径,称两个顶点强连通(strongly connected).如果有向图G的每两个顶点都强连通,称G是一个强连通图.非强连通图有向图的极大强连通子图,称为强连通分量(strongly connected components). 下图中,子图{1,2,3,4}为一个强连通分量,因为顶点1,2,3,4两两可达.{5},{6}也分别是两个强连通分量. 大体来说有3中算法Kosaraju,Trajan,Gabow这三种!后续文章中将相继