关于Tarjan算法

梗概

tarjan算法有两种(我了解的),一种是用来求强连通分量的,另一种是关于割点和桥的问题。

根据机房大佬HL说过,这两种算法是互相独立的,只是代码很像。


强连通分量问题

关于这类tarjan算法,我了解到的主要的一个应用就是缩点。

例题传送门

思路

首先,如果我们考虑,如果这是一个有向无环图,我们可以用拓扑排序(DP?)的方法直接求出答案。

但是这个图是一个有向有环图,无法直接采用拓扑排序。

这时候我们想为什么无法直接用拓扑排序,因为在有环的情况下我们无法直接确定各个点的拓扑序。

如果我们把每个环都替换成一个点,点权为环中点权的总和,这就是一个有向无环图。

怎么样才可以找到这些环呢?这时候就可以拿出tarjan了。

tarjan算法

tarjan算法有两个关键的数组:dfn[]和low[]。

dfn[i]表示搜索到第i个点时的时间戳(第几个搜到的)。

low[i]表示第i个点所在的环中的所有点的最小dfn[]。

上代码:

void dfs(int x){
dfn[x]=low[x]=++Tt;q[++t]=x;vis[x]=1;//Tt为时间戳 q[]为栈数组
//vis[i]表示i点是否在栈中
for(int i=lnk[x];i;i=la[i]){
int y=ne[i];
if(!dfn[y]){dfs(y);low[x]=min(low[x],low[y]);}
//如果该节点没被遍历过,就查看x是否与y在同一个环中。
else if(vis[y])low[x]=min(low[x],dfn[y]);
//否则,就查看x与y是否成环。
}
if(low[x]==dfn[x]){
++cnt;
int y=0;
while(y!=x){y=q[t];ty[y]=cnt;A[cnt]+=a[y];vis[y]=0;--t;}
//这里为染色的过程,所有环都有一个颜色,后面根据每个点的颜色重新建图。
}
}

重新建图完毕后就可以直接进行拓扑排序。

参考程序

#include<bits/stdc++.h>
using namespace std;
int read(){
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=1;c=getchar();}
while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
return x*f;
}
int dfn[10005],low[10005],a[10005],q[100005],A[10005],ty[10005],t,tot,cnt,n,m,h,ans,Tt;
int ne[100005],la[100005],lnk[10005],Ne[100005],La[100005],Lnk[10005],du[10005],dis[10005];
bool vis[300005];
void add(int x,int y){ne[++tot]=y;la[tot]=lnk[x];lnk[x]=tot;}
void Add(int x,int y){Ne[++tot]=y;La[tot]=Lnk[x];Lnk[x]=tot;}
void dfs(int x){
dfn[x]=low[x]=++Tt;q[++t]=x;vis[x]=1;
for(int i=lnk[x];i;i=la[i]){
int y=ne[i];
if(!dfn[y]){dfs(y);low[x]=min(low[x],low[y]);}
else if(vis[y])low[x]=min(low[x],dfn[y]);
}
if(low[x]==dfn[x]){
++cnt;
int y=0;
while(y!=x){y=q[t];ty[y]=cnt;A[cnt]+=a[y];vis[y]=0;--t;}
}
}
void bfs(){
h=t=0;
memset(vis,0,sizeof(vis));
for(int i=1;i<=cnt;++i)if(du[i]==0&&(!vis[i]))q[++t]=i,dis[i]=A[i],vis[i]=1;
while(h<t){
int u=q[++h];
for(int k=Lnk[u];k;k=La[k]){
du[Ne[k]]--;
if(dis[Ne[k]]<dis[u]+A[Ne[k]])dis[Ne[k]]=dis[u]+A[Ne[k]];
if(du[Ne[k]]==0&&(!vis[Ne[k]]))q[++t]=Ne[k];
}
}
for(int i=1;i<=cnt;++i)ans=max(ans,dis[i]);
}
int main(){
n=read(),m=read();for(int i=1;i<=n;++i)a[i]=read();
for(int i=1;i<=m;++i){int x=read(),y=read();add(x,y);}
tot=0;for(int i=1;i<=n;++i)if(!dfn[i])dfs(i);
tot=0;for(int i=1;i<=n;++i)for(int j=lnk[i];j;j=la[j])if(ty[i]!=ty[ne[j]])Add(ty[i],ty[ne[j]]),++du[ty[ne[j]]];
bfs();
cout<<ans;
return 0;
}

割点和桥问题

首先了解几个概念

割点 :在去掉这个点以及它所关联的边后,图从连通变为不连通,这个点就被称为割点。

:在去掉这条边后,图从连通变为不连通,这条边就被称为桥。

例题传送门

思路

这一类问题题目中的条件是无向图。这是和上面一种Tarjan的一个很大的区别。

以下是求割点的代码。

void tar(int u,int fa){//fa作为根节点作为一个特殊的情况
dfn[u]=low[u]=++cnt;int son=0;
for(int j=lnk[u];j;j=la[j]){
int v=ne[j];
if(!dfn[v]){//找出了一个环
tar(v,fa);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]&&u!=fa)cut[u]=1;//如果u的v子树和u不成环u为一个割点
if(u==fa)++son;
}
low[u]=min(low[u],dfn[v]);
}
if(son>1&&u==fa)cut[fa]=1;
}

接下来是求桥的代码。

void tar(int u){
dfn[u]=low[u]=++sum;
for(int j=lnk[u];j;j=la[j]){
vis[j]=1;
if(!vis[j^1]){
int v=ne[j];
if(!dfn[v]){
tar(v);
low[u]=min(low[u],low[v]);
if(dfn[u]<low[v])cut[j]=cut[j^1]=1;
}else low[u]=min(low[u],dfn[v]);
}
}
}

求割点和求桥的在代码上区别其实就一个等于号,如果背板子的话也比较简单。

例题参考程序:

#include<bits/stdc++.h>
using namespace std;
const int N=20005,M=200005;
int read(){
int x=0;char c=getchar();
while(c<'0'||c>'9')c=getchar();
while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
return x;
}
int n,m,ans;
int ne[M],la[M],lnk[N],tot;
int dfn[N],low[N],cnt;
bool cut[N];
void add(int x,int y){ne[++tot]=y;la[tot]=lnk[x];lnk[x]=tot;}
void tar(int u,int fa){
dfn[u]=low[u]=++cnt;int son=0;
for(int j=lnk[u];j;j=la[j]){
int v=ne[j];
if(!dfn[v]){
tar(v,fa);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]&&u!=fa)cut[u]=1;
if(u==fa)++son;
}
low[u]=min(low[u],dfn[v]);
}
if(son>1&&u==fa)cut[fa]=1;
}
int main(){
n=read();m=read();
for(int i=1;i<=m;++i){
int x=read(),y=read();
add(x,y);add(y,x);
}
for(int i=1;i<=n;++i)if(!dfn[i])tar(i,i);
for(int i=1;i<=n;++i)if(cut[i])++ans;printf("%d\n",ans);
for(int i=1;i<=n;++i)if(cut[i])printf("%d ",i);
return 0;
}

关于tarjan的更多相关文章

  1. HDU4738 tarjan割边|割边、割点模板

    题目:http://acm.hdu.edu.cn/showproblem.php?pid=4738 坑点: 处理重边 图可能不连通,要输出0 若求出的结果是0,则要输出1,因为最少要派一个人 #inc ...

  2. bzoj 1179[Apio2009]Atm (tarjan+spfa)

    题目 输入 第一行包含两个整数N.M.N表示路口的个数,M表示道路条数.接下来M行,每行两个整数,这两个整数都在1到N之间,第i+1行的两个整数表示第i条道路的起点和终点的路口编号.接下来N行,每行一 ...

  3. tarjan讲解(用codevs1332(tarjan的裸题)讲解)

    主要借助这道比较裸的题来讲一下tarjan这种算法 tarjan是一种求解有向图强连通分量的线性时间的算法.(用dfs来实现) 如果两个顶点可以相互通达,则称两个顶点强连通.如果有向图G的每两个顶点都 ...

  4. NOIP2009最优贸易[spfa变形|tarjan 缩点 DP]

    题目描述 C 国有 n 个大城市和 m 条道路,每条道路连接这 n 个城市中的某两个城市.任意两个 城市之间最多只有一条道路直接相连.这 m 条道路中有一部分为单向通行的道路,一部分 为双向通行的道路 ...

  5. Tarjan

    //求强连通分量 void uni(int x,int y){ if (rank[x]<rank[y]){ fa[x]=y; size[y]+=size[x]; }else{ rank[x]+= ...

  6. 【UOJ#67】新年的毒瘤 Tarjan 割点

    #67. 新年的毒瘤 UOJ直接黏贴会炸...    还是戳这里吧: http://uoj.ac/problem/67#tab-statement Solution 看到这题的标签就进来看了一眼. 想 ...

  7. 【Codefoces487E/UOJ#30】Tourists Tarjan 点双连通分量 + 树链剖分

    E. Tourists time limit per test: 2 seconds memory limit per test: 256 megabytes input: standard inpu ...

  8. 【BZOJ-1123】BLO Tarjan 点双连通分量

    1123: [POI2008]BLO Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 970  Solved: 408[Submit][Status][ ...

  9. 【BZOJ-2730】矿场搭建 Tarjan 双连通分量

    2730: [HNOI2012]矿场搭建 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1602  Solved: 751[Submit][Statu ...

  10. Tarjan三把刀

    搞过OI的对tarjan这个人大概都不陌生.这个人发明了很多神奇的算法,在OI届广被采用. 他最广泛采用的三个算法都是和$dfn$,$low$相关的. 有向图求强连通分量 其实说直白点,就是缩点.用得 ...

随机推荐

  1. canvas学习笔记,实用知识点总结(上)

    本博客是本人日常学习笔记,作为重要知识点的总结记录,随笔风格可能更倾向于个人的学习习惯和方式,若对您有帮助十分荣幸,若存在问题欢迎互相学习探讨,前端小白一枚在此恭候. 一.基本使用规则 1.创建画布 ...

  2. 深入理解 GIL:如何写出高性能及线程安全的 Python 代码

    深入理解 GIL:如何写出高性能及线程安全的 Python 代码 本文由 伯乐在线 - 郑芸 翻译.未经许可,禁止转载!英文出处:A. Jesse.欢迎加入翻译组. GIL对多线程的影响:http:/ ...

  3. 使用apidoc生成项目文档

    [1]npm install apidoc -g 全局安装apidoc [2]apidoc -v 查看是否安装成功 [3]apidoc.json apidoc的项目级配置文件,它必须位于整个工程目录顶 ...

  4. js-sha256源码

    /** * [js-sha256]{@link https://github.com/emn178/js-sha256} * * @version 0.9.0 * @author Chen, Yi-C ...

  5. Docker 介绍及基础命令

    Docker 简介 Docker 是一个开源项目,诞生于 2013 年初,最初是 dotCloud 公司内部的一个业余项目.它基于 Google 公司推出的 Go 语言实现. 项目后来加入了 Linu ...

  6. linux 常用命令集锦

    喝断片儿了,我是谁?我在什么地方?我做过些什么事?查看当前用户 who am i查看当前路径 pwd查看历史记录 history 我忘了程序放哪了,就记得个名.更新系统数据库 updatedb查找文件 ...

  7. CF451E Devu and Flowers

    多重集求组合数,注意到\(n = 20\)所以可以用\(2 ^ n * n\)的容斥来写. 如果没有限制那么答案就是\(C(n + s - 1, n - 1)\).对每一个限制依次考虑,加上有一种选多 ...

  8. marquee标签的使用

    marquee语法    <marquee></marquee> 实例一<marquee>Hello, World</marquee> marquee常 ...

  9. python之路(11)描述符

    前言 描述符是用于代理另一个类的属性,一般用于大型的框架中,在实际的开发项目中较少使用,本质是一个实现了__get__(),__set__(),__delete__()其中一个方法的新式类 __get ...

  10. 对半导体制造(FAB)工种的全方位解析

    本文转载自微信公众号 - 感集网, 链接 https://mp.weixin.qq.com/s/MRoWRbKZFBrJcQAZPqDa7w