Tarjan系列!我愿称Tarjan为爆搜之王!

1.Tarjan求LCA

利用并查集在一遍DFS中可以完成所所有询问。是一种离线算法。

遍历到一个点时,我们先将并查集初始化,再遍历完一个子树之后,将该子树的根的父亲指向当前点。

最后在回溯的时候给询问的答案更新一下,枚举一下 \(v\in [1,n]\) 的点,看是否有询问,如果有询问更新一下 \(LCA[u][v]=LCA[v][u]=find(v);\) 但是前提是 \(v\) 已经被访问。

我们优化一下枚举的点,放入vector优化一下,就是 \(O(n+m)\) 的复杂度了。

测试用题:

T1

T2

T1 Code:

但是经过事实证明,有速度排行:

树剖 < st表 < 倍增 < Tarjan

什么东西天下第一不用我多说了吧。

  1. #include<bits/stdc++.h>
  2. #define ll long long
  3. #define db double
  4. #define filein(a) freopen(#a".in","r",stdin)
  5. #define fileot(a) freopen(#a".out","w",stdout)
  6. template<class T>
  7. inline void read(T &s){
  8. s=0;char ch=getchar();bool f=0;
  9. while(ch<'0'||'9'<ch) {if(ch=='-') f=1;ch=getchar();}
  10. while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=getchar();}
  11. s=f?-s:s;
  12. if(ch=='.'){
  13. db p=0.1;ch=getchar();
  14. while('0'<=ch&&ch<='9') {s=s+(ch^48)*p;p*=0.1;ch=getchar();};
  15. }
  16. }
  17. using std::vector;
  18. const int N=5e5+1;
  19. int n,m,rt;
  20. vector<int>head,nxt;
  21. struct Edge{
  22. int u,v;
  23. };vector<Edge>to;
  24. inline void join(int u,int v){
  25. nxt.push_back(head[u]);
  26. head[u]=to.size();
  27. to.push_back({u,v});
  28. }
  29. struct ques{
  30. int id,x;
  31. };
  32. vector<ques>qu[N];
  33. int ans[N];
  34. int fa[N];
  35. int find(int x){
  36. return x==fa[x]?x:fa[x]=find(fa[x]);
  37. }
  38. bool vis[N];
  39. void dfs(int u){
  40. fa[u]=u;
  41. vis[u]=1;
  42. for(int i=head[u];~i;i=nxt[i]){
  43. int v=to[i].v;
  44. if(vis[v]) continue;
  45. dfs(v);
  46. fa[v]=u;
  47. }
  48. for(auto it:qu[u]){
  49. int id=it.id,x=it.x;
  50. if(vis[x]){
  51. ans[id]=find(fa[x]);
  52. }
  53. }
  54. }
  55. int main(){
  56. filein(b);fileot(b);
  57. read(n);read(m);read(rt);
  58. head.resize(n+1,-1);
  59. for(int i=1;i<n;++i){
  60. int u,v;read(u);read(v);
  61. join(u,v);join(v,u);
  62. }
  63. for(int i=1;i<=m;++i){
  64. int u,v;read(u);read(v);
  65. qu[u].push_back({i,v});
  66. qu[v].push_back({i,u});
  67. }
  68. dfs(rt);
  69. for(int i=1;i<=m;++i){
  70. printf("%d\n",ans[i]);
  71. }
  72. return 0;
  73. }

T2:我们考虑树上差分即可,这个我在之前的博客提到过了,就不多说了。

树上差分

2.Tarjan求割点、割边、强连通分量

概念

割点:删除点 \(u\) 后使得连通图变为非连通图,该点为割点。

如果一个图没有割点,那么叫重连通图,每个顶点间至少2条路径(除非点数<3)。

强连通指有向图中的连通图,无向图中叫连通图。

割点

然后Tarjan可以在DFS中完成割点查找。我们给点赋予时间戳 \(dfn\) ,就得到了深度优先生成树。

我们称能到达 \(dfn\) 小于自己的边叫回边。

而对于一个点 \(u\) 要么是根要么非根。先考虑 \(u\) 为根,若其为割点,其满足拥有两个及以上的子树。这个结论是显然的,可以用链和一颗根有多个儿子的树来举例分析。

如果 \(u\) 不是根,\(u\) 为割点,那么其子树中存在有的点没有到达 \(u\) 祖先的回边。

因为没有回边到达 \(u\) 以上,那么删除这个点 \(u\) 之后必定能使得图非联通。(具体就是这个点出不去了)

考虑怎么判断这个条件。我们用时间戳 \(dfn\) 和最低时间戳 \(low\) 来辅助。

其中:

\[low[u]=min\{dfn[u],min\{low[v]|v是u的儿子\},min\{dfn[v]|(u,v)为一条回边\}\}
\]

那为什么 \(low\) 取不到 \(min\{low[v]|(u,v)为一条回边\}\) ? 原因其实很简单,首先是要根据其定义,然后就考虑为什么这样设计。比如说当我们求割点的时候,割点可以通过回边到达一个 \(dfn\) 更小的点,但是我们既然删掉了这个点那就不影响了。如果这是其子树正好有到达 \(u\) 点的回边,如果随着其 \(low\) 跳到那个更低的点,那么结果是有问题的,这是会把 \(u\) 误判为非割点。

再考虑这样设计为什么不会错,因为我们这样已经足够判断其是否会通向其他子树了。

  • 在 \(u\) 非 \(dfn\) 树上的根时,若存在 \(v\) 是 \(u\) 的儿子,使 \(dfn[u]<=low[v]\) ,那么 \(u\) 是割点。

考虑反证,若 \(low[v]<dfn[u]\) ,那么子树中必定有向 \(u\) 祖先的回边,因此 \(u\) 非割点。

然后考虑一下根有多个子树的情况即可。

  1. #include<bits/stdc++.h>
  2. #define ll long long
  3. #define db double
  4. #define filein(a) freopen(#a".in","r",stdin)
  5. #define fileot(a) freopen(#a".out","w",stdout)
  6. template<class T>
  7. inline void read(T &s){
  8. s=0;char ch=getchar();bool f=0;
  9. while(ch<'0'||'9'<ch) {if(ch=='-') f=1;ch=getchar();}
  10. while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=getchar();}
  11. s=f?-s:s;
  12. if(ch=='.'){
  13. db p=0.1;ch=getchar();
  14. while('0'<=ch&&ch<='9') {s=s+(ch^48)*p;p*=0.1;ch=getchar();};
  15. }
  16. }
  17. using std::vector;
  18. const int N=2e4+1;
  19. vector<int>head,nxt;
  20. struct Edge{
  21. int u,v;
  22. };vector<Edge>to;
  23. int n,m;
  24. inline void join(int u,int v){
  25. nxt.push_back(head[u]);
  26. head[u]=to.size();
  27. to.push_back({u,v});
  28. }
  29. int idx;
  30. int dfn[N],low[N];bool cut[N];
  31. void dfs(int u,int f){
  32. dfn[u]=low[u]=++idx;
  33. int child=0;
  34. for(int i=head[u];~i;i=nxt[i]){
  35. int v=to[i].v;
  36. if(!dfn[v]){
  37. dfs(v,u);
  38. if(dfn[u]<=low[v] and u!=f){
  39. cut[u]=1;
  40. }
  41. low[u]=std::min(low[u],low[v]);
  42. if(u==f) ++child;
  43. }else if(v!=f){
  44. low[u]=std::min(low[u],dfn[v]);
  45. }
  46. }
  47. if(child>=2 and u==f){
  48. cut[u]=1;
  49. }
  50. }
  51. int main(){
  52. filein(b);fileot(b);
  53. read(n);read(m);
  54. head.resize(n+1,-1);
  55. for(int i=1;i<=m;++i){
  56. int u,v;read(u);read(v);
  57. join(u,v);join(v,u);
  58. }
  59. for(int i=1;i<=n;++i){
  60. if(!dfn[i]){
  61. idx=0;
  62. dfs(i,i);
  63. }
  64. }
  65. int ans=0;
  66. for(int i=1;i<=n;++i){
  67. if(cut[i]) ++ans;
  68. }
  69. printf("%d\n",ans);
  70. for(int i=1;i<=n;++i){
  71. if(cut[i]){
  72. printf("%d ",i);
  73. }
  74. }putchar('\n');
  75. return 0;
  76. }

割边

删掉一条边后连通图变为非联通的,那么这条边称为割边(或桥)。

在 \(dfs\) 树上,\(u\) 为 \(v\) 的父亲节点,那么 \((u,v)\) 是割边的条件为:\(u\) 到 \(v\) 的边不是重边且 \(v\) 及其子孙节点中没有向 \(u\) 及其祖先的回边。

因为这次删的是边,而不是点,所以不能反到 \(u\),不等式不能取到等号。

类似的,若 \(dfn[u]<low[v]\),那么 \((u,v)\) 是割边。

由于没有模板再加上和上面的类似,我就只写核心代码了。

  1. void dfs(int u){
  2. dfn[u]=low[u]=++idx;
  3. for(int i=head[u];~i;i=nxt[i]){
  4. int v=to[i].v;
  5. if(!dfn[v]){
  6. dfs(v);
  7. if(dfn[u]<low[v]){
  8. cut[i]=1;
  9. }
  10. low[u]=std::min(low[u],low[v]);
  11. }else if(v!=f){
  12. low[u]=std::min(low[u],dfn[v]);
  13. }
  14. }
  15. }

割点与割边

  • 两个割点之间的边是割边吗。割边的两个端点是割点吗。

举反例即可。第一很显然是错的,因为我们断掉一个点和断掉一条边是不等价的,上面我们论证 \(low\) 的时候举了这样的例子,就不多赘述了。第二个我们发现也是有问题的,虽然我们断点的时候肯定会断掉这条边,显然如果那边的点在删完这个点之后删空了,那么也是不满足的。

强连通分量

有向图各个节点互相可达,那么叫做强连通分量。

一遍DFS求出。若有节点 \(u\) 满足 \(low[u]==dfn[u]\) 那么其子树不可能到达其祖先。那么这个 \(u\) 为一个强连通分量在 \(dfs\) 搜索树中的根。其中有多个强连通分量,其子树与一个强连通分量不一定完全相等。我们递归将其下的强连通分量记录并去除,最后以 \(a\) 为根又有一个强连通分量。

其下的强连通分量按照相同方法找到。

我们用栈来找出一个强连通分量中的点(弹出其下的强连通分量中的点,一直弹到弹掉其下强连通分量的根为止)。

代码我们和缩点一起给出。

缩点

强连通分量内可以进行缩点,然后形成DAG(有向无环图)。能有更多优秀的性质。

缩点我们给同一个强连通分量内的点染成同一色,然后检查每条边的两边的点的颜色。同色忽略,异色就对于两个颜色建一条边即可完成缩点。

以模板题T1为例

我们这个时候处理回边的方法需要修改一下。因为是有向图。我们设立一个标记看一个点是否在栈中,如果在栈中那么其为回边,否则不为回边(因为对方已经被分入一个强连通分量中)。

缩点后变为DAG,就可以使用拓扑排序了。

基本的拓扑排序不难,看一眼就会了

拓扑排序

T1

T2

  1. #include<bits/stdc++.h>
  2. #define ll long long
  3. #define db double
  4. #define filein(a) freopen(#a".in","r",stdin)
  5. #define fileot(a) freopen(#a".out","w",stdout)
  6. template<class T>
  7. inline void read(T &s){
  8. s=0;char ch=getchar();bool f=0;
  9. while(ch<'0'||'9'<ch) {if(ch=='-') f=1;ch=getchar();}
  10. while('0'<=ch&&ch<='9') {s=s*10+(ch^48);ch=getchar();}
  11. s=f?-s:s;
  12. if(ch=='.'){
  13. db p=0.1;ch=getchar();
  14. while('0'<=ch&&ch<='9') {s=s+(ch^48)*p;p*=0.1;ch=getchar();};
  15. }
  16. }
  17. using std::vector;
  18. const int N=1e4+1,M=1e5+1;
  19. int n,m;
  20. int a[N];
  21. vector<int>head,nxt;
  22. struct Edge{
  23. int u,v,w;
  24. };vector<Edge>to;
  25. Edge e[M];
  26. inline void join(int u,int v){
  27. nxt.push_back(head[u]);
  28. head[u]=to.size();
  29. to.push_back({u,v});
  30. }
  31. int idx;
  32. int dfn[N],low[N];
  33. int st[N],top;
  34. int col[N];
  35. int num;
  36. int sum[N];
  37. bool ink[N];
  38. void dfs(int u){
  39. dfn[u]=low[u]=++idx;
  40. st[++top]=u;
  41. ink[u]=1;
  42. for(int i=head[u];~i;i=nxt[i]){
  43. int v=to[i].v;
  44. if(!dfn[v]){
  45. dfs(v);
  46. low[u]=std::min(low[u],low[v]);
  47. }else if(ink[v]){
  48. low[u]=std::min(low[u],dfn[v]);
  49. }
  50. }
  51. if(low[u]==dfn[u]){
  52. ++num;
  53. while(1){
  54. col[st[top] ]=num;
  55. sum[num]+=a[st[top] ];
  56. ink[st[top] ]=0;
  57. //printf("%d ",st[top]);
  58. if(st[top]==u){
  59. --top;
  60. //printf("(%d)",sum[num]);
  61. //putchar('\n');
  62. break;
  63. }
  64. --top;
  65. }
  66. }
  67. }
  68. int res=0;
  69. void dfs2(int u){
  70. res+=sum[u];
  71. for(int i=head[u];~i;i=nxt[i]){
  72. int v=to[i].v;
  73. dfs(v);
  74. }
  75. }
  76. int dp[N];
  77. int ind[N];
  78. std::queue<int>q;
  79. int ans=0;
  80. inline void topo(){
  81. for(int i=1;i<=num;++i){
  82. if(!ind[i]){
  83. dp[i]=sum[i];
  84. ans=std::max(dp[i],ans);
  85. q.push(i);
  86. }
  87. }
  88. while(!q.empty() ){
  89. int u=q.front();q.pop();
  90. for(int i=head[u];~i;i=nxt[i]){
  91. int v=to[i].v;
  92. --ind[v];
  93. if(ind[v]==0){
  94. dp[v]=dp[u]+sum[v];
  95. q.push(v);
  96. ans=std::max(ans,dp[v]);
  97. }
  98. }
  99. }
  100. }
  101. inline bool cmp(Edge x,Edge y){
  102. if(x.u==y.u) return x.v<y.v;
  103. return x.u<y.u;
  104. }
  105. int main(){
  106. filein(b);fileot(b);
  107. read(n);read(m);
  108. head.resize(n+1,-1);
  109. for(int i=1;i<=n;++i){
  110. read(a[i]);
  111. }
  112. for(int i=1;i<=m;++i){
  113. int u,v;read(u);read(v);
  114. join(u,v);
  115. }
  116. for(int i=1;i<=n;++i){
  117. if(!dfn[i]){
  118. idx=0;
  119. dfs(i);
  120. }
  121. }
  122. m=0;
  123. for(int i=0;i<to.size();++i){
  124. if(col[to[i].u]!=col[to[i].v]){
  125. e[++m]={col[to[i].u],col[to[i].v]};
  126. }
  127. }
  128. head.clear();nxt.clear();to.clear();
  129. head.resize(num+1,-1);
  130. std::sort(e+1,e+1+m,cmp);
  131. for(int i=1;i<=m;++i){
  132. if(i>1 and e[i].u==e[i-1].u and e[i].v==e[i-1].v)
  133. continue;
  134. //printf("%d %d\n",e[i].u,e[i].v);
  135. join(e[i].u,e[i].v);
  136. ++ind[e[i].v];
  137. }
  138. topo();
  139. printf("%d\n",ans);
  140. return 0;
  141. }

Tarjan入门的更多相关文章

  1. hdu 1269 (强联通分量Tarjan入门)

    迷宫城堡 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submis ...

  2. POJ 2168 Popular cows [Tarjan 缩点]

                                                                                                         ...

  3. 「刷题笔记」Tarjan

    贴一个讲得非常详细的\(tarjan\)入门教程 信息传递 讲个笑话:我之前用并查集求最小环过的这题,然后看见题目上有个\(tarjan\)标签 留下了深刻的印象:\(tarjan\)就是并查集求最小 ...

  4. 【小白入门向】tarjan算法+codevs1332上白泽慧音 题解报告

    一.[前言]关于tarjan tarjan算法是由Robert Tarjan提出的求解有向图强连通分量的算法. 那么问题来了找蓝翔!(划掉)什么是强连通分量? 我们定义:如果两个顶点互相连通(即存在A ...

  5. HDU-2586-裸LCA入门-tarjan离线

    http://acm.hdu.edu.cn/showproblem.php?pid=2586 给出一颗树和边权,询问两点距离. 考虑tarjan离线做法,做法很巧妙,当前进行到u,对他的儿子v,当v子 ...

  6. HDU 1827 Summer Holiday(tarjan求强连通分量+缩点构成新图+统计入度+一点贪心思)经典缩点入门题

    Summer Holiday Time Limit: 10000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)T ...

  7. 2-sat入门(tarjan)hdu(3062)

    hdu3062 Party Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) To ...

  8. LCA离线Tarjan,树上倍增入门题

    离线Tarjian,来个JVxie大佬博客最近公共祖先LCA(Tarjan算法)的思考和算法实现,还有zhouzhendong大佬的LCA算法解析-Tarjan&倍增&RMQ(其实你们 ...

  9. Tarjan缩点入门

    缩点 顾名思义,缩点就是把一个强连通分量缩成一个点 Tarjan 在dfs的过程中记录时间戳,若能够通过某个点返回已遍历的点,则可以缩点 inline void Tarjan(int x)// st栈 ...

随机推荐

  1. android JS 互相通讯

    1.android中利用webview调用网页上的js代码. Android 中可以通过webview来实现和js的交互,在程序中调用js代码,只需要将webview控件的支持js的属性设置为true ...

  2. 微信小程序开发快速入手

    1.在page中的修改数据的setData函数,需要传递的是一个对象. that.setData({ src: res.tempFilePath }) 2.在 onload 事件中,可以获取wx.na ...

  3. C#内置委托类型Func和Action对比及用法

    C#的内置委托类型 Func Action 返回值 有(只有一个Tresult) 无 重载 17个(0参-16参) 17个(0参-16参) 泛型 支持 支持 系统内置 是 是 是否需要声明 否 否 c ...

  4. java中如何使用接口继承(Extending Interfaces)

    5.接口继承(Extending Interfaces)和通话talk的功能.而Moto888更为高级,除了照相和通话功能以外,还有mp3的功能.接口继承到底有什么意义呢?马克-to-win:1)通过 ...

  5. java中如何知道一个字符串中有多少个字,把每个字打印出来,举例

    9.6 About string,"I am ateacher",这个字符串中有多少个字,且分别把每个字打印出来. public class Test {     static i ...

  6. Mybatis实现简单增删改查

    Mybatis的简单应用 学习内容: 需求 环境准备 代码 总结: 学习内容: 需求 使用Mybatis实现简单增删改查(以下是在IDEA中实现的,其他开发工具中,代码一样) jar 包下载:http ...

  7. 变量 数据类型 条件if语句

    python是解释型  弱类型编程语言;  "优雅", "明确", "简单";  开发效率非常高;  可移植性;  可扩展性; 可嵌入型. ...

  8. springboot+maven实现模块化编程

    1.创建新项目repo-modele 2.右键Repo_modele -> New -> Module-->next 分别创建bs-web,bs-service,bs-entity, ...

  9. switch 用法

    1.语法格式和规则 switch case 语句语法格式如下: switch(expression){ case value : //语句 break; //可选 case value : //语句 ...

  10. asp.net core启动源码以及监听,到处理请求响应的过程

    摘要 asp.net core发布至今已经将近6年了,很多人对于这一块还是有些陌生,或者说没接触过:接触过的,对于asp.net core整个启动过程,监听过程,以及请求过程,响应过程也是一知半解,可 ...