前言

tarjan是一种神奇的算法,

它可以在线性时间内求强联通分量/缩点/LCA/割点/割边/...

但由于博主咸鱼,暂时掌握不了这么多,

先讲讲其中最简单的一些。


概述

tarjan是以DFS为基础的算法。

在DFS的过程中会产生一棵树。

tarjan在DFS过程中要用到2个数组(其实还有一个栈):

low[],节点i能够回溯到的最早位于栈中的节点;

dfn[],深度优先搜索遍历时节点i被搜索的次序。

在搜索过程中,对于任意节点a和与其相连的节点b,根据节点b是否在栈中来进行不同的操作:

if(b不是a的子树)low[a]=min(low[a],dfn[b]);
else low[a]=min(low[a],low[b]);

得到这2个数组,接下来就好办了。

缩点

就是找强联通分量。

在tarjan搜索的时候有一个栈,记录访问过的节点,

当dfn[i]==low[i]时,为i或i的子树可以构成一个强连通分量,可以开始弹栈。

由于该连通图中的dfn值和low值相等的节点是该连通分量中第一个被访问到的节点,

又根据栈的特性,则该节点在最里面。

所以能够通过不停的弹栈,直到弹出这个节点来找到该连通分量中所有的节点。

而在不停弹栈时要记录一下这些节点所在的强连通分量。

注意:tarjan之后的图已经不能用了用了不等于没缩点,因此缩点之后要重新连边

裸题:luogu 2746 https://www.luogu.org/problem/show?pid=2746

代码蒯上

#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gotcha()
{
register int _a=0;bool _b=1;register char _c=getchar();
while(_c<'0' || _c>'9'){if(_c=='-')_b=0;_c=getchar();}
while(_c>='0' && _c<='9')_a=_a*10+_c-48,_c=getchar();
return _b?_a:-_a;
}
const int _ = 102,__ = 10002;
struct nextstar{int to,ne;nextstar(){to=ne=0;}}e[__];
int he[_]={0},ecnt=0;
void add(int fr,int to)
{
e[++ecnt].to=to;
e[ecnt].ne=he[fr];
he[fr]=ecnt;
}
int co[_],cocnt,dfn[_]={0},low[_]={0},cnt=0,sta[_],rr=0,m,n;
bool in[_]={0},out[_]={0},ed[_]={0};
void tarjan(int now)
{
dfn[now]=low[now]=++cnt;
sta[++rr]=now;ed[now]=1;
int i,j;
for(i=he[now];i;i=e[i].ne)
{
j=e[i].to;
if(!dfn[j])tarjan(j),low[now]=min(low[now],low[j]);
else if(ed[j])low[now]=min(low[now],dfn[j]);
}
if(low[now]==dfn[now])
{
cocnt++,i=0;
while(i!=now){i=sta[rr--];co[i]=cocnt;ed[i]=0;}
}
}
int main()
{
register int i,j,ans=0,ans2=0;
n=gotcha();
for(i=1;i<=n;i++)
{
j=gotcha();
while(j!=0){add(i,j),j=gotcha();}
}
m=ecnt;
for(i=1;i<=n;i++)if(!dfn[i])rr=0,tarjan(i);
for(i=1;i<=n;i++)
for(j=he[i];j;j=e[j].ne)
if(co[i]!=co[e[j].to])out[co[i]]=1,in[co[e[j].to]]=1;
if(cocnt==1){printf("%s","1\n0");return 0;}
for(i=1;i<=cocnt;i++)
{
if(!in[i])ans++;
if(!out[i])ans2++;
}
printf("%d\n%d",ans,max(ans,ans2));
return 0;
}

割点/割边

若low[b]>=dfn[a],则a为割点。

节点b的子孙和节点a形成一个块。因为这说明b的子孙不能够通过其他边到达a的祖先,这样去掉a之后,图必然分裂为两个子图。

这样我们处理点a时,首先递归a的子节点b,然后从b回溯至a后,如果发现上述不等式成立,则找到了一个割点a,并且a和b的子树构成一个块。

注意:如果a是根节点要特判一下。即如果它的子树大于1棵,辣么它也是个割点。

若low[b]>dfn[a],则(a,b)为割边。

但是实际处理时可能有重边,因此需要。

我们记录每条边的标号与每个点的父亲到它的边的标号,

如果边(a,b)是b的父亲边,就不能用dfn[a]更新low[b]。这样如果遍历完b的所有子节点后,发现low[b]仍然=dfn[b],说明u的父亲边(a,b)为割边。

因为博主咸鱼,只写了割点的裸题 luogu 3388,https://www.luogu.org/problem/show?pid=3388

有时间把割边的补上。

代码蒯上

#include<iostream>
#include<iomanip>
#include<cmath>
#include<map>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gotcha()
{
register int _a=0;bool _b=1;register char _c=getchar();
while(_c<'0' || _c>'9'){if(_c=='-')_b=0;_c=getchar();}
while(_c>='0' && _c<='9')_a=_a*10+_c-48,_c=getchar();
return _b?_a:-_a;
}
const int _ = 100002;
struct nextstar
{
int to,ne;
nextstar(){to=ne=0;}
}e[2*_];
int he[_]={0},ecnt=0;
void add(int fr,int to)
{
e[++ecnt].to=to;
e[ecnt].ne=he[fr];
he[fr]=ecnt;
}
int dfn[_]={0},low[_]={0},cnt=0,fa[_],n,m;
bool cut[_]={0};
void trajan(int now)
{
int i,j,in=0;
dfn[now]=low[now]=++cnt;
for(i=he[now];i;i=e[i].ne)
{
j=e[i].to;
if(!dfn[j])
{
fa[j]=fa[now];
trajan(j);
low[now]=min(low[now],low[j]);
if(low[j]>=dfn[now] && now!=fa[now])cut[now]=1;
if(now==fa[now])in++;
}
low[now]=min(low[now],dfn[j]);
}
if(now==fa[now] && in>=2)cut[fa[now]]=1;
}
int main()
{
register int i,j,k,ans=0;
n=gotcha(),m=gotcha();
for(i=1;i<=n;i++)fa[i]=i;
for(i=1;i<=m;i++)
{
j=gotcha(),k=gotcha();
add(j,k),add(k,j);
}
for(i=1;i<=n;i++)if(!dfn[i])trajan(i);
for(i=1;i<=n;i++)if(cut[i])ans++;
printf("%d\n",ans);
for(i=1;i<=n;i++)if(cut[i])printf("%d ",i);
return 0;
}

LCA

tarjan求LCA当然是离线的啦(想在线的出门右转学倍增)

tarjan求lca还要用并查集。

求lca(a,b),当返回到节点i时,必然已经访问完了i的子树。此时将i的子树上的节点的父亲记为i。

这样递归下去,当a和b第一次被标记时,i就是它俩的最近公共祖先。

从根节点向下搜索时,搜索完一个节点时,与其父亲节点连(并查集的)边,放入并查集。

再判断a节点对应的要询问的节点是否已经被访问过。若已访问过,那么最近公共祖先就是b节点在并查集中的父亲。

想了解更多去 http://www.cnblogs.com/JVxie/p/4854719.html

裸题:luogu 3379 https://www.luogu.org/problem/show?pid=3379

代码蒯上

#include<iostream>
#include<iomanip>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
inline int gotcha()
{
register int _a=0;bool _b=1;register char _c=getchar();
while(_c<'0' || _c>'9'){if(_c=='-')_b=0;_c=getchar();}
while(_c>='0' && _c<='9')_a=_a*10+_c-48,_c=getchar();
return _b?_a:-_a;
}
inline void launch(int _a,bool _ret)
{
int _num=0;char _c[15];
while(_a)_c[++_num]=(_a%10)+48,_a/= 10;
while(_num)putchar(_c[_num--]);if(_ret)putchar('\n');
}
const int _ = 500002;
struct nextstar
{
int to,ne;
nextstar(){to=ne=0;}
}e[2*_];
struct trouble
{
int to,ne,num;
trouble(){to=ne=num=0;}
}tr[2*_];
int h1[_]={0},h2[_]={0},ecnt=0,trcnt=0;
void add(int fr,int to)
{
e[++ecnt].to=to;
e[ecnt].ne=h1[fr];
h1[fr]=ecnt;
}
void newtrouble(int fr,int to,int num)
{
tr[++trcnt].to=to;
tr[trcnt].num=num;
tr[trcnt].ne=h2[fr];
h2[fr]=trcnt;
}
int fa[_],ans[_]={0},root,m,n;
bool ed[_]={0};
int finder(int a){return a!=fa[a]?fa[a]=finder(fa[a]):a;}
void tarjan(int now)
{
int i;ed[now]=1;
for(i=h2[now];i;i=tr[i].ne)
if(ed[tr[i].to])ans[tr[i].num]=finder(fa[tr[i].to]);
for(i=h1[now];i;i=e[i].ne)if(!ed[e[i].to])tarjan(e[i].to),fa[e[i].to]=now;
}
int main()
{
register int i,j,k;
n=gotcha(),m=gotcha(),root=gotcha();
for(i=1;i<=n;i++)fa[i]=i;
for(i=1;i<n;i++)
{
j=gotcha(),k=gotcha();
add(j,k),add(k,j);
}
for(i=1;i<=m;i++)
{
j=gotcha(),k=gotcha();
newtrouble(j,k,i),newtrouble(k,j,i);
}
tarjan(root);
for(i=1;i<=m;i++)launch(ans[i],1);//want to try it?
return 0;
}

暂时先写这么多,

由于博主咸鱼,这东西不定期更新。

tarjan - tarjan的几种用法的更多相关文章

  1. [算法模版]Tarjan爷爷的几种图论算法

    [算法模版]Tarjan爷爷的几种图论算法 前言 Tarjan爷爷发明了很多图论算法,这些图论算法有很多相似之处(其中一个就是我都不会).这里会对这三种算法进行简单介绍. 定义 强连通(strongl ...

  2. using 的三种用法

    using 有哪三种用法? 1)引入命名空间. 2)给命名空间或者类型起别名. 3)划定作用域.自动释放资源,使用该方法的类型必须实现了 System.IDisposable接口,当对象脱离作用域之后 ...

  3. c++ operator操作符的两种用法:重载和隐式类型转换,string转其他基本数据类型的简洁实现string_cast

    C++中的operator主要有两个作用,一是操作符的重载,一是自定义对象类型的隐式转换.对于操作符的重载,许多人都不陌生,但是估计不少人都不太熟悉operator的第二种用法,即自定义对象类型的隐式 ...

  4. Wix 安装部署教程(十五) --CustomAction的七种用法

    在WIX中,CustomAction用来在安装过程中执行自定义行为.比如注册.修改文件.触发其他可执行文件等.这一节主要是介绍一下CustomAction的7种用法. 在此之前要了解InstallEx ...

  5. Android Intent的几种用法全面总结

    Android Intent的几种用法全面总结 Intent, 用法 Intent应该算是Android中特有的东西.你可以在Intent中指定程序要执行的动作(比如:view,edit,dial), ...

  6. Js闭包常见三种用法

        Js闭包特性源于内部函数可以将外部函数的活动对象保存在自己的作用域链上,所以使内部函数的可以将外部函数的活动对象占为己有,可以在外部函数销毁时依然存有外部函数内的活动对象内容,这样做的好处是可 ...

  7. operator 的两种用法

    C++,有时它的确是个耐玩的东东,就比如operator,它有两种用法,一种是operator overloading(操作符重载),一种是operator casting(操作隐式转换).1.操作符 ...

  8. Service的两种用法及其生命周期

    先来一点基础知识: Service 是android的四大组件之一,与Activity同属于一个级别,它是运行在后台进行服务的组件(例如在后台播放的音乐,播放音乐的同时并不影响其他操作).Servic ...

  9. C#中this的 四种 用法

    C#中的this用法,相信大家应该有用过,但你用过几种?以下是个人总结的this几种用法,欢迎大家拍砖,废话少说,直接列出用法及相关代码. this用法1:限定被相似的名称隐藏的成员 /// < ...

  10. js正则表达式中的问号几种用法小结

    这篇文章主要介绍了js正则表达式中的问号几种用法,比如+?,*?,{2,3}?可以停止匹配的贪婪模式,感兴趣的朋友可以参考下 在表示重复的字符后面加问号,比如+?,*?,{2,3}?可以停止匹配的贪婪 ...

随机推荐

  1. AJPFX总结Java 类加载器

    顾名思义,类加载器(class loader)用来加载 Java 类到 Java 虚拟机中.一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java ...

  2. 13.JAVA-包package、import使用

    1.包的定义 之前我们学习java时,生成的class文件都是位于当前目录中,假如出现了同名文件,则会出现文件覆盖问题,因此就需要设置不同的目录(定义包),来解决同名文件冲突问题. 并且在大型项目中, ...

  3. linux下指定特定用户执行命令

    虽然很简单但是百度找的大部分不能用,我是没找到,后来从google找到的 sudo -H -u www bash -c 'nohup /home/web/ke/upfileserver /home/w ...

  4. JavaScript 事件对象event

    什么是事件对象? 比如当用户单击某个元素的时候,我们给这个元素注册的事件就会触发,该事件的本质就是一个函数,而该函数的形参接收一个event对象. 注:事件通常与函数结合使用,函数不会在事件发生前被执 ...

  5. 用Node+wechaty写一个爬虫脚本每天定时给女(男)朋友发微信暖心话

    wechatBot 微信每日说,每日自动发送微信消息给你心爱的人 项目介绍 灵感来源 在掘金看到了一篇<用Node + EJS写一个爬虫脚本每天定时女朋友发一封暖心邮件>后, 在评论区偶然 ...

  6. 关于如何将html中的表格下载成csv格式的方法

    今天在网上看了很多方法,自己还是慢慢探索写出了最终效果 简单代码如下: <!DOCTYPE html> <html> <head> <meta content ...

  7. Mac版 Slickedit 2013 v18.0.3.3 破解

    今天在Windows机器上面,无调试器的情况下,把 Mac系统下的Slickedit给破解了并测试通过. 原始安装包下载: Mac Slickedit 2013 (v18.0.3.3) 破解文件下载地 ...

  8. xcode在代码中查找中文

    总是忘记xcode中查找中文,这次记下来,以后就不会忘记了,哈哈 请看下图: 切换到查找,点击find后面的text,选择Regular Expression,然后输入 1. 查找非ascii的字符 ...

  9. cpp 计算程序运行时间的两种方法

    1. #include <time.h> time_t begin_t = clock(); // to do time_t finish_t = clock(); cout<< ...

  10. mtDNA|ctDNA|cpDNA|

    5.9细胞器基因组是编码细胞器蛋白质的环状DNA分子 细胞器中除真核细胞线粒体DNA(mtDNA)是线性的外,都是环状分子,比如叶绿体DNA(ctDNA,cpDNA).因为单个细胞器有几套不同拷贝的细 ...