割点 —— 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=,x=;
while(ch<''||ch>'')
{
if(ch=='-') x=-x;
ch=getchar();
}
while(ch>=''&&ch<='')
{
a=(a<<)+(a<<)+(ch-'');
ch=getchar();
}
return a*x;
}
int n,m,tim,top,edge_sum,scc_sum,tot;
int head[],dfn[],low[],vis[],st[],u[],v[],ans[];
queue<int> q;
struct node
{
int from,to,next;
}a[];
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=; //记录根节点有几棵互不相连的子树
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>) ans[u]=; //如果不是根节点,那么直接就是割点了;如果是根节点,并且互不相连的子树个数超过1棵,则也是割点
}
}
else low[u]=min(low[u],dfn[v]);
}
}
int main()
{
n=read();m=read();
for(int i=;i<=m;i++)
{
u[i]=read();
v[i]=read();
add(u[i],v[i]); //建双向边
add(v[i],u[i]);
}
for(int i=;i<=n;i++) //有可能图不是联通的,要多进行几次Tarjan
{
if(!dfn[i]) tarjan(i,i);
}
for(int i=;i<=n;i++) //求割点个数
{
if(ans[i]) tot++;
}
printf("%d\n",tot);
for(int i=;i<=n;i++)
{
if(ans[i]) printf("%d ",i); //输出每个割点
}
return ;
}
再看个例题:
题目大意
给定一张无向图,求每个点被封锁之后有多少个有序点对 ( 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=,x=;
while(ch<''||ch>'')
{
if(ch=='-') x=-x;
ch=getchar();
}
while(ch>=''&&ch<='')
{
a=(a<<)+(a<<)+(ch-'');
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]=; //求以点u为根的树的大小
long long child=,tot=;
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]-); //统计这些子树内的结点对外结点的贡献
tot+=size[v]; //统计这些子树内的结点总数
if(u!=root||child>) yes[u]=; //如果u不是根节点,那么直接就是割点了;如果是根节点,并且有超过1棵子树的话,也是割点
}
}
else low[u]=min(low[u],dfn[v]);
}
if(yes[u]) ans[u]+=tot*(n-tot-)+*(n-); //tot*(n-tot-1)是外结点对子树内结点的贡献,2*(n-1)是点u对所有点的贡献
}
int main()
{
n=read();m=read();
for(int i=;i<=m;i++)
{
u[i]=read();v[i]=read();
add(u[i],v[i]);
add(v[i],u[i]);
}
root=;
tarjan(); //题目的信息说明了这个图原本是联通的,所以我们求一次Tarjan就好了
for(int i=;i<=n;i++) if(!yes[i]) ans[i]=*(n-); //不是割点的话,答案就是2*(n-1)
for(int i=;i<=n;i++) printf("%lld\n",ans[i]);
return ;
}
希望CSP不要考到这一块的内容啊qwq
最后祝大家 CSP rp ++
割点 —— Tarjan 算法的更多相关文章
- 洛谷3388 【模板】割点 tarjan算法
题目描述 给出一个n个点,m条边的无向图,求图的割点. 关于割点 在无向连通图中,如果将其中一个点以及所有连接该点的边去掉,图就不再连通,那么这个点就叫做割点(cut vertex / articul ...
- zoj 1119 / poj 1523 SPF (典型例题 求割点 Tarjan 算法)
poj : http://poj.org/problem?id=1523 如果无向图中一个点 u 为割点 则u 或者是具有两个及以上子女的深度优先生成树的根,或者虽然不是一个根,但是它有一个子女 w, ...
- 割点和桥---Tarjan算法
使用Tarjan算法求解图的割点和桥. 1.割点 主要的算法结构就是DFS,一个点是割点,当且仅当以下两种情况: (1)该节点是根节点,且有两棵以上的子树; (2)该节 ...
- tarjan算法--求无向图的割点和桥
一.基本概念 1.桥:是存在于无向图中的这样的一条边,如果去掉这一条边,那么整张无向图会分为两部分,这样的一条边称为桥无向连通图中,如果删除某边后,图变成不连通,则称该边为桥. 2.割点:无向连通图中 ...
- Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载)
Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载) 转载自:http://hi.baidu.com/lydrainbowcat/blog/item/2 ...
- tarjan算法(割点/割边/点连通分量/边连通分量/强连通分量)
tarjan算法是在dfs生成一颗dfs树的时候按照访问顺序的先后,为每个结点分配一个时间戳,然后再用low[u]表示结点能访问到的最小时间戳 以上的各种应用都是在此拓展而来的. 割点:如果一个图去掉 ...
- Tarjan算法:求解图的割点与桥(割边)
简介: 割边和割点的定义仅限于无向图中.我们可以通过定义以蛮力方式求解出无向图的所有割点和割边,但这样的求解方式效率低.Tarjan提出了一种快速求解的方式,通过一次DFS就求解出图中所有的割点和割边 ...
- 『Tarjan算法 无向图的割点与割边』
无向图的割点与割边 定义:给定无相连通图\(G=(V,E)\) 若对于\(x \in V\),从图中删去节点\(x\)以及所有与\(x\)关联的边后,\(G\)分裂为两个或以上不连通的子图,则称\(x ...
- tarjan算法(强连通分量 + 强连通分量缩点 + 桥(割边) + 割点 + LCA)
这篇文章是从网络上总结各方经验 以及 自己找的一些例题的算法模板,主要是用于自己的日后的模板总结以后防失忆常看看的, 写的也是自己能看懂即可. tarjan算法的功能很强大, 可以用来求解强连通分量, ...
随机推荐
- 机器码-字节码-CLR-JIT-托管代码-非托管代码-unsafe-GC-fixed
0. 机器码 直接由机器码对应平台的CPU执行的指令集, 因此无法在其他指令集的CPU上运行. 无法跨平台. 由本地代码编译得到. (托管代码通过JIT生成) 1. 字节码 即 bytecode 是一 ...
- excel2016打开为空白界面解决办法
前言 excel2016打开文件为空白的界面,明显不正常. 解决方法 https://blog.csdn.net/b2345012/article/details/94134401 以上.
- UI5-技术篇-Hybrid App-2-Geolocation位置定位
在SAP WEB IDE简单测试基于HTML5自带的定位功能,相关步骤如下: 1.VIEW代码 <mvc:View xmlns:core="sap.ui.core" xmln ...
- MySQL Replication--开启GTID模式下匿名事务异常
错误环境: OS: CentOS release 6.5 (Final) MySQL: MySQL 5.7.19 主从参数配置: master_info_repository = TABLE rela ...
- out string
示例 当希望方法返回多个值时,声明 out 方法很有用.使用 out 参数的方法仍然可以将变量用作返回类型(请参见 return),但它还可以将一个或多个对象作为 out 参数返回给调用方法.此示例使 ...
- Spring Boot 笔记 (2) - 使用 log4j2 记日志
日志框架的选用 Spring 使用的默认日志框架是 logback, 默认情况下会采取默认的 autoconfiguration; 即便想对日志的一些配置进行修改也比较方便, 详细可以参考: Spri ...
- p2.BTC-数据结构
hash pointers:哈希指针,除了保存值的地址,还要存这整个区块的内容的hash值.这样就既能访问到值,还能确定访问的值有没有被篡改. 一 Blockchain Block chain is ...
- SecureCRT中文乱码解决已设置UTF-8了
参考网址:http://www.iitshare.com/securecrt-chinese-garbled-solution.html 问题描述 SecureCRT与SecureFX的常规选项里面已 ...
- 【Flask】 python学习第一章 - 3.0 正则转换和错误捕捉
3.1正则转换器定义 Class RegexConverter(BaseConverter): regex = "[0-9]{6}" app.url_map.converters[ ...
- 14.专攻python和centos7
大牛博客:https://blog.51cto.com/yangrong/p5 用户接口应用程序: [root@python01 ~]# ps aux|grep bashroot 23860 ...