Tarjan求强连通分量

先来一波定义

强连通:有向图中A点可以到达B点,B点可以到达A点,则称为强连通

强连通分量:有向图的一个子图中,任意两个点可以相互到达,则称当前子图为图的强连通分量

强连通图: 如果在一个有向图中,每两个点都强连通,我们就叫这个图叫强连通图。

(一张十分简洁的图)

如图,图中{1,2}就是一个强连通,也是这个图中的一个强连通分量

求强连通分量的算法有三种:

Kosaraju算法,Tarjan算法,Gabow算法(然而我只会用Tarjan求)

这里就稍微介绍一下tarjan求强连通分量

首先要明白几个概念:

时间戳(dfn):dfn就是在搜索中某一节点被遍历到的次序号,

节点能追溯到的最早的栈中节点的时间戳(low):low就是某一节点在栈中能追溯到的最早的父亲节点的时间戳。

Tarjan算法就是基于深度优先搜索的算法:把没有标记过时间戳的点放入栈中,然后以它为根进行搜索,搜索完后回溯更新low,保证low一定是最小的,如果发现low[u] == dfn[u],就把u上面的全部弹出栈内,这些点就构成了强连通分量,而这个u就是一个关键节点:从这个节点能够到达其强连通分量中的其他节点,但是没有其他属于这个强连通分量以外的点能够到达这个点,所以这个点的low[u]值维护完了之后还是和dfn[u]的值一样

这样描述还是太抽象了点,还是走一遍流程吧

cnt为计数器

一张经典的图:

搜到1,1入栈,dfn[1] = low[1] = ++cnt,栈内元素:1

搜到2,2没被搜过,2入栈,dfn[2] = low[2] = ++cnt,栈内元素:1,2

搜到3,3没被搜过,3入栈,dfn[3] = low[3] = ++cnt,站内元素:1,2,3

搜到6,6没被搜过,6入栈,dfn[6] = low[6] = ++cnt,站内元素:1,2,3,6

6没有出边,此时

发现low[6] == dfn[6],把6弹出,6为强连通分量

回溯到3,发现low[3] == dfn[3],把3弹出,3为强连通分量

回溯到2,发现2还可以到5,搜索5

搜到5,5没被搜过,5入栈,dfn[5] = low[5] = ++cnt,栈内元素1,2,5

搜到1,发现1被搜过了且在栈内,因为low是节点能追溯到的最早的栈中节点的时间戳,我们要保证low最小,所以我们要更新low[5] = min(low[5],dfn[1]) = 1

再搜到6,6被搜过但不再栈内,不管

没有其他边了,回溯,回溯到2,因为low要最小,而后面的low值要<=当前的,所以用后面的更新当前的,更新2的值low[2] = min(low[2],low[5])

回溯到1

继续搜,搜到4,4没有被搜过,4入栈,栈内元素:1,2,5,4

再搜到5,发现5在栈内,就更新low[4],low[4] = min(low[4],dfn[5]) = 5

没有其他的边了,然后回溯,回溯到1

发现dfn[1] == low[1]

于是乎我们将所有1上面的元素及1弹栈,得到一个强连通分量1,2,5,4

于是这个图中就有3个强连通分量

代码:

  1. //vis[i]表示i是否在栈中
  2. //sum表示是连通块个数
  3. //size[i]表示第i个连通块内的元素个数
  4. //bel[i]表示i属于哪个连通块belong也可以理解为color
  5. stack<int>s;
  6. void tarjan(int u) {
  7. dfn[u] = low[u] = ++ cnt;
  8. vis[u] = ;//标记是否在栈内
  9. s.push(u);
  10. for(int i = head[u]; ~i; i = e[i].nx) {
  11. int v = e[i].v;
  12. if(!dfn[v]) tarjan(v),low[u] = min(low[u],low[v]);
  13. else if(vis[v]) low[u] = min(low[u],dfn[v]);
  14. }
  15. if(low[u] == dfn[u]) {
  16. ;sum ++;
  17. while(x != u) {//x枚举栈内元素
  18. x = s.top(),s.pop();
  19. vis[x] = ,bel[x] = sum;
  20. size[sum] ++;
  21. }
  22. }
  23. }

但因为并不一定所有的点都相连,如

所以我们遍历的时候要:

  1. ; i <= n; ++ i) if(!dfn[i]) tarjan(i);

例题:

很多题都会用到缩点的思想,所以这里先给出两个练手的

HDU1269迷宫城堡

  1. //求强连通分量的个数是不是等于1
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. ;
  5. int n,m,num,cnt,sum;
  6. int dfn[N],low[N],head[N],bel[N];
  7. bool vis[N];
  8. struct node {
  9. int v,nx;
  10. }e[N];
  11.  
  12. template<class T>inline void read(T &x) {
  13. x = ;;char ch =getchar();
  14. while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
  15. + ch - ',ch = getchar();
  16. x = f ? -x : x;
  17. return ;
  18. }
  19.  
  20. inline void add(int u,int v) {
  21. e[++num].nx = head[u],e[num].v = v,head[u] = num;
  22. }
  23.  
  24. stack<int>s;
  25. void tarjan(int u) {
  26. dfn[u] = low[u] = ++cnt;
  27. vis[u] = ;
  28. s.push(u);
  29. for(int i = head[u]; ~i; i = e[i].nx) {
  30. int v = e[i].v;
  31. if(!dfn[v]) tarjan(v),low[u] = min(low[u],low[v]);
  32. else if(vis[u]) low[u] = min(dfn[v],low[u]);
  33. }
  34. if(dfn[u] == low[u]) {
  35. ;sum ++;
  36. while(u != x) {
  37. x = s.top(),s.pop();
  38. vis[x] = ,bel[x] = sum;
  39. }
  40. }
  41. }
  42.  
  43. int main() {
  44. while(scanf("%d%d",&n,&m)) {
  45. && m == ) break;
  46. memset(head,-,sizeof(head));
  47. memset(vis,,sizeof(vis));
  48. memset(dfn,,sizeof(dfn));
  49. memset(low,,sizeof(low));
  50. memset(bel,,sizeof(bel));
  51. memset(e,,sizeof(e));
  52. num = sum = cnt = ;
  53. ,x,y; i <= m; ++ i) read(x),read(y),add(x,y);
  54. ; i <= n; ++ i) if(!dfn[i]) tarjan(i);
  55. ) printf("Yes\n");
  56. else printf("No\n");
  57. }
  58. ;
  59. }

HDU 1269

P2863 [USACO06JAN]牛的舞会The Cow Prom

  1. //求联通块内的元素个数是否大于1
  2. #include <iostream>
  3. #include <cstdio>
  4. #include <cstring>
  5. #include <stack>
  6. using namespace std;
  7. const int N = 1e6;
  8. int n,m,num,cnt,tot,sum,ans;
  9. int a[N],b[N],head[N],size[N],bel[N],dfn[N],low[N];
  10. bool vis[N];
  11. stack<int>s;
  12. struct node {
  13. int v,nx;
  14. }e[N];
  15.  
  16. template<class T>inline void read(T &x) {
  17. x = ;;char ch = getchar();
  18. while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
  19. + ch - ',ch = getchar();
  20. x = f ? -x : x;
  21. return ;
  22. }
  23.  
  24. inline void add(int u,int v) {
  25. e[++num].nx = head[u],e[num].v = v,head[u] = num;
  26. }
  27.  
  28. void tarjan(int u) {
  29. dfn[u] = low[u] = ++cnt;
  30. vis[u] = ;
  31. s.push(u);
  32. for(int i = head[u]; ~i; i = e[i].nx) {
  33. int v = e[i].v;
  34. if(!dfn[v]) tarjan(v),low[u] = min(low[u],low[v]);
  35. else if(vis[u]) low[u] = min(dfn[v],low[u]);
  36. }
  37. if(dfn[u] == low[u]) {
  38. int x;sum ++;
  39. while(u != x) {
  40. x = s.top(),s.pop();
  41. vis[x] = ,bel[x] = sum;
  42. size[sum] ++;
  43. }
  44. }
  45. }
  46.  
  47. int main() {
  48. memset(head,-,sizeof(head));
  49. read(n),read(m);
  50. ,x,y; i <= m; i ++) {
  51. read(x),read(y);
  52. add(x,y);
  53. }
  54. ; i <= n; i ++) if(!dfn[i]) tarjan(i);
  55. ; i <= sum; i ++) {
  56. )
  57. ans ++;
  58. }
  59. printf("%d",ans);
  60. ;
  61. }

P2863

Tarjan缩点

个人觉得分不分开讲一个样,缩点就是在上面的内容之后附加一个操作

其实缩点可以看做是一种解题方法罢

简单说,就是把一个强连通分量里的点做为一个点处理

也就是把bel数组里的元素当成点看就好了

假设我们有一张图长这个样:

那我们操作的时候就可以脑补成这样

---->

emmmm.....一般题目都会说如果相互到达就balabalbala

具体代码呢,就是枚举某个点的连边连向的点,如果在同一分量里,就不予处理,否则就按题目要求处理

  1. ; i <= n; ++ i)
  2. for(int j = head[i]; ~j; j = e[j].nx) {
  3. int v = e[j].v;
  4. if(bel[i] != bel[v]) /*.code.*/
  5. }

异常的容易

那就直接看题目吧:

P2002 消息扩散

  1. //求入度为0的点的个数
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. ;
  5. ;
  6. int n,m,cnt,sum,ans,num;
  7. int head[N],dfn[N],low[N],bel[N],in[N];
  8. bool vis[N];
  9. struct node {
  10. int v,nx;
  11. }e[M];
  12.  
  13. template<class T>inline void read(T &x) {
  14. x = ;;char ch = getchar();
  15. while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
  16. + ch - ',ch = getchar();
  17. x = f ? -x : x;
  18. return ;
  19. }
  20.  
  21. inline void add(int u,int v) {
  22. e[++num].nx = head[u],e[num].v = v,head[u] = num;
  23. }
  24.  
  25. stack<int>s;
  26. void tarjan(int u) {
  27. dfn[u] = low[u] = ++cnt;
  28. vis[u] = ;
  29. s.push(u);
  30. for(int i = head[u]; ~i; i = e[i].nx) {
  31. int v = e[i].v;
  32. if(!dfn[v]) tarjan(v),low[u] = min(low[u],low[v]);
  33. else if(vis[v]) low[u] = min(low[u],dfn[v]);
  34. }
  35. if(low[u] == dfn[u]) {
  36. int x;sum ++;
  37. while(x != u) {
  38. x = s.top(),s.pop();
  39. vis[x] = ,bel[x] = sum;
  40. }
  41. }
  42. }
  43.  
  44. int main() {
  45. memset(head,-,sizeof(head));
  46. read(n),read(m);
  47. ,x,y; i <= m; ++ i) {
  48. read(x),read(y);
  49. if(x == y) continue;
  50. add(x,y);
  51. }
  52. ; i <= n; ++ i) if(!dfn[i]) tarjan(i);
  53. ; i <= n; ++ i)
  54. for(int j = head[i]; ~j; j = e[j].nx) {
  55. int v = e[j].v;
  56. if(bel[i] != bel[v]) in[bel[v]] ++;
  57. }
  58. // for(int i = 1; i <= n; ++ i) cout<<in[bel[i]]<<" ";
  59. ; i <= sum; ++ i) if(!in[i]) ans ++;
  60. cout<<ans;
  61. ;
  62. }

P2002

P2341 [HAOI2006]受欢迎的牛

  1. //找出度为0的点
  2. //代码历史久远,以2002为准
  3. #include <iostream>
  4. #include <cstdio>
  5. #include <cstring>
  6. #include <stack>
  7. using namespace std;
  8. const int N = 1e6;
  9. int n,m,tot,sum,ans,cnt,num;
  10. int a[N],b[N],head[N],dfn[N],low[N],size[N],bel[N];
  11. bool vis[N],mark[N];
  12. struct node {
  13. int v,nx;
  14. }e[N];
  15.  
  16. inline void add(int u,int v) {
  17. e[++num].nx = head[u],e[num].v = v,head[u] = num;
  18. }
  19. stack<int>s;
  20. void tarjan(int u) {
  21.  
  22. dfn[u] = low[u] = ++cnt;
  23. vis[u] = ;
  24. s.push(u);
  25. for(int i = head[u]; ~i; i = e[i].nx) {
  26. int v = e[i].v;
  27. if(!dfn[v]) tarjan(v),low[u] = min(low[u],low[v]);
  28. else if(vis[v]) low[u] = min(low[u],dfn[v]);
  29. }
  30. if(dfn[u] == low[u]) {
  31. int x;sum ++;
  32. while(u != x) {
  33. x = s.top(),s.pop();
  34. vis[x] = ,bel[x] = sum;
  35. size[sum] ++;
  36. }
  37. }
  38. }
  39.  
  40. int main() {
  41. memset(head,-,sizeof(head));
  42. scanf("%d%d",&n,&m);
  43. ; i <= m; i ++) {
  44. scanf("%d%d",&a[i],&b[i]);
  45. add(a[i],b[i]);
  46. }
  47. ; i <= n; i ++) if(!dfn[i]) tarjan(i);
  48. ; i <= m; i ++) ;
  49. ; i <= sum; i ++) if(!mark[i]) ans = size[i],tot++;
  50. ) printf("%d",ans);
  51. ");
  52. ;
  53. }

P2341

P2194 HXY烧情侣

  1. /*
  2. tarjan
  3. 第一问求所有联通块的块中最小值之和,开一个mn数组在tarjan中记录
  4. 根据乘法原理,第二问求所有块中 价值=最小值的个数 的乘积
  5. */
  6. #include <bits/stdc++.h>
  7. #define ll long long
  8. using namespace std;
  9. const int N = 1e6;
  10. ;
  11. ll n,m,num,cnt,sum,ans,ans1 = ;
  12. ll mn[N],dfn[N],low[N],bel[N],size[N],a[N],head[N];
  13. bool vis[N];
  14. struct node {
  15. ll v,nx;
  16. }e[N];
  17.  
  18. template<class T>inline void read(T &x) {
  19. x = ;ll f = ;char ch = getchar();
  20. while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
  21. + ch - ',ch = getchar();
  22. x = f ? -x : x;
  23. return;
  24. }
  25.  
  26. inline void add(ll u,ll v) {
  27. e[++num].nx = head[u],e[num].v = v,head[u] = num;
  28. }
  29.  
  30. stack<ll>s;
  31. void tarjan(ll u) {
  32. dfn[u] = low[u] = ++ cnt;
  33. vis[u] = ;
  34. s.push(u);
  35. for(ll i = head[u]; ~i; i = e[i].nx) {
  36. ll v = e[i].v;
  37. if(!dfn[v]) tarjan(v),low[u] = min(low[u],low[v]);
  38. else if(vis[v]) low[u] = min(low[u],dfn[v]);
  39. }
  40. if(low[u] == dfn[u]) {
  41. ll x=-;sum ++;
  42. while(x != u) {
  43. x = s.top(),s.pop();
  44. bel[x] = sum,vis[x] = ;
  45. if(mn[sum] == a[x]) size[sum] ++;
  46. else if(mn[sum] > a[x]) {
  47. mn[sum] = a[x];
  48. size[sum] = ;
  49. }
  50. }
  51. }
  52. }
  53.  
  54. int main() {
  55. memset(head,-,sizeof(head));
  56. read(n);
  57. ; i <= n; ++ i) mn[i] = 0x7fffffff;
  58. ; i <= n; ++ i) read(a[i]);
  59. read(m);
  60. ,x,y; i <= m; ++ i) read(x),read(y),add(x,y);
  61. ; i <= n; ++ i) if(!dfn[i]) tarjan(i);
  62. ; i <= sum; ++ i) {
  63. ans += mn[i];
  64. ans1 = ans1 * size[i] % mod;
  65. }
  66. cout<<ans<<" "<<ans1;
  67. ;
  68. }

P2194

P2746 [USACO5.3]校园网Network of Schools

  1. //同样历史久远
  2. //1.求缩点后入度为0 的点的个数
  3. //2.求入度为0的点数与出度为0的点的较大值
  4. #include <bits/stdc++.h>
  5. using namespace std;
  6. ;
  7. int n,m,num,sum,cnt,j,ans,ans1;
  8. int dfn[N],low[N],size[N],bel[N],x,kk[N][N],head[N];
  9. bool vis[N],mark[N],mark1[N];
  10. struct node {
  11. int v,nx;
  12. }e[N];
  13.  
  14. template<class T>inline void read(T &x) {
  15. x = ;;char ch = getchar();
  16. while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
  17. + ch - ',ch = getchar();
  18. x = f ? -x : x;
  19. return;
  20. }
  21.  
  22. inline void add(int u,int v) {
  23. e[++num].nx = head[u],e[num].v = v,head[u] = num;
  24. }
  25.  
  26. stack<int>s;
  27. void tarjan(int u) {
  28. dfn[u] = low[u] = ++cnt;
  29. s.push(u);
  30. vis[u] = ;
  31. for(int i = head[u]; ~i; i = e[i].nx) {
  32. int v = e[i].v;
  33. if(!dfn[v]) tarjan(v),low[u] = min(low[u],low[v]);
  34. else if(vis[v]) low[u] = min(low[u],dfn[v]);
  35. }
  36. if(dfn[u] == low[u]) {
  37. int x;sum ++;
  38. while(x != u) {
  39. x = s.top(),s.pop();
  40. vis[x] = ,bel[x] = sum;
  41. size[sum] ++;
  42. }
  43. }
  44. }
  45.  
  46. int main() {
  47. memset(head,-,sizeof(head));
  48. read(n);
  49. ; i <= n; ++ i) {
  50. ; j <= n + ; ++ j) {
  51. read(x);
  52. ) break;
  53. kk[i][j] = x;
  54. add(i,x);
  55. }
  56. }
  57. ; i <= n; ++ i) if(!dfn[i]) tarjan(i);
  58. ; i <= n; ++ i) {
  59. ; kk[i][j] != ; j ++) ,mark1[bel[i]] = ;
  60. }
  61. ) {
  62. printf("%d\n0",sum);
  63. ;
  64. }
  65. ; i <= sum; ++ i) {
  66. if(!mark[i]) ans ++;
  67. if(!mark1[i]) ans1 ++;
  68. }
  69. printf("%d\n%d",ans,max(ans,ans1));
  70. }

P2194

P2812 校园网络【[USACO]Network of Schools加强版】

  1. //邻接表
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. ;
  5. ;
  6. int n,num,sum,cnt,ans1,ans2;
  7. int low[N],dfn[N],head[N],size[N],bel[N];
  8. bool vis[N],mark[N],mark1[N];
  9. struct node {
  10. int v,nx;
  11. }e[M];
  12.  
  13. template<class T>inline void read(T &x) {
  14. x = ;;char ch = getchar();
  15. while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
  16. + ch - ',ch = getchar();
  17. x = f ? -x : x;
  18. return ;
  19. }
  20.  
  21. inline void add(int u,int v) {
  22. e[++num].nx = head[u],e[num].v = v,head[u] = num;
  23. }
  24.  
  25. stack<int>s;
  26. void tarjan(int u) {
  27. dfn[u] = low[u] = ++ cnt;;
  28. vis[u] = ;
  29. s.push(u);
  30. for(int i = head[u]; ~i; i = e[i].nx) {
  31. int v = e[i].v;
  32. if(!dfn[v]) tarjan(v),low[u] = min(low[u],low[v]);
  33. else if(vis[v]) low[u] = min(low[u],dfn[v]);
  34. }
  35. if(dfn[u] == low[u]) {
  36. ;sum ++;
  37. while(x != u) {
  38. x = s.top(),s.pop();
  39. bel[x] = sum,vis[x] = ;
  40. size[sum] ++;
  41. }
  42. }
  43. }
  44.  
  45. int main(int argc, char const *argv[]) {
  46. memset(head,-,sizeof(head));
  47. read(n);
  48. ,x; i <= n; ++ i) {
  49. ; j <= n + ; ++ j) {
  50. read(x);
  51. ) break;
  52. add(i,x);
  53. }
  54. }
  55. ; i <= n; ++ i) if(!dfn[i]) tarjan(i);
  56. ; i <= n; ++ i) {
  57. for(int j = head[i]; ~j; j = e[j].nx) {
  58. if(bel[i] != bel[e[j].v]) {
  59. mark[bel[e[i].v]] = ;
  60. mark1[bel[i]] = ;
  61. }
  62. }
  63. }
  64. ; i <= sum; ++ i) {
  65. if(!mark[i]) ans1 ++;
  66. if(!mark1[i]) ans2 ++;
  67. }
  68. printf("%d\n%d\n", ans1,ans2);
  69. ;
  70. }

P2812

P1407 [国家集训队]稳定婚姻

  1. /*
  2. 主体还是一个tarjan
  3. 因为最后还会组成n对情侣的话
  4. 找恋人的人和恋人和男方会在一个联通块内
  5. 因为男女必须成对出现,所以联通块内的点得个数为偶数,所以联通块内每个人都会配对
  6. 经过路径是boy1->girl2->boy2->gril1->boy1
  7. */
  8. #include <bits/stdc++.h>
  9. using namespace std;
  10. ;
  11. ;
  12. int n,m,k,num,sum,cnt,tmp;
  13. int head[N],dfn[N],bel[N],low[N],sta[N];
  14. bool vis[N];
  15. string g,b;
  16. map<string,int>mp;
  17. struct node {
  18. int v,nx;
  19. }e[M];
  20.  
  21. template<class T>inline void read(T &x) {
  22. x = ;;char ch = getchar();
  23. while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
  24. + ch - ',ch = getchar();
  25. x = f ? -x : x;
  26. return ;
  27. }
  28.  
  29. inline void add(int u,int v) {
  30. e[++num].nx = head[u],e[num].v = v,head[u] = num;
  31. }
  32.  
  33. stack<int>s;
  34. void tarjan(int u) {
  35. dfn[u] = low[u] = ++cnt;
  36. vis[u] = ;
  37. s.push(u);
  38. for(int i = head[u]; ~i; i = e[i].nx) {
  39. int v = e[i].v;
  40. if(!dfn[v]) tarjan(v),low[u] = min(low[u],low[v]);
  41. else if(vis[v]) low[u] = min(low[u],dfn[v]);
  42. }
  43. if(dfn[u] == low[u]) {
  44. int x;sum ++;
  45. while(u != x) {
  46. x = s.top(),s.pop();
  47. vis[x] = ,bel[x] = sum;
  48. }
  49. }
  50. }
  51.  
  52. int main() {
  53. memset(head,-,sizeof(head));
  54. read(n);
  55. ; i <= n; ++ i) {
  56. cin>>g>>b;
  57. mp[g] = i,mp[b] = i + n;
  58. add(i,i + n);
  59. }
  60. read(m);
  61. ; i <= m; ++ i) {
  62. cin>>g>>b;
  63. add(mp[g],mp[b]),add(mp[b],mp[g]);
  64. }
  65. ; i <= n * ; ++ i) if(!dfn[i]) tarjan(i);
  66. ; i <= n; ++ i) {
  67. if(bel[i] == bel[i + n]) printf("Unsafe\n");
  68. else printf("Safe\n");
  69. }
  70. ;
  71. }

P1407

以下两个可能用到其他小知识

P2169 正则表达式

  1. //tarjan+最短路
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. ;
  5. ;
  6. const int INF = 0x3f3f3f3f;
  7. int n,m,sum,num,num_new,cnt;
  8. int dfn[N],low[N],head[N],head_new[N],bel[N],dis[N];
  9. bool vis[N],vis_new[N];
  10. struct edge {
  11. int v,nx,w;
  12. }e[N];
  13. struct edge_new {
  14. int v,nx,w;
  15. }e_new[N];
  16. struct node {
  17. int id,dis;
  18. bool operator < (const node &l) const {
  19. return dis > l.dis;
  20. }
  21. };
  22.  
  23. template<class T>inline void read(T &x) {
  24. x = ;;char ch = getchar();
  25. while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
  26. + ch - ',ch = getchar();
  27. x = f ? -x : x;
  28. return;
  29. }
  30.  
  31. inline void add(int u,int v,int w) {
  32. e[++num].nx = head[u],e[num].v = v,e[num].w = w,head[u] = num;
  33. }
  34.  
  35. inline void add_new(int u,int v,int w) {
  36. e_new[++num_new].nx = head_new[u],e_new[num_new].v = v,e_new[num_new].w = w,head_new[u] = num_new;
  37. }
  38.  
  39. stack<int>s;
  40. void tarjan(int u) {
  41. dfn[u] = low[u] = ++ cnt;
  42. vis[u] = ;
  43. s.push(u);
  44. for(int i = head[u]; ~i; i = e[i].nx) {
  45. int v = e[i].v;
  46. if(!dfn[v]) tarjan(v),low[u] = min(low[u],low[v]);
  47. else if(vis[v]) low[u] = min(low[u],dfn[v]);
  48. }
  49. if(low[u] == dfn[u]) {
  50. int x;sum ++;
  51. while(u != x) {
  52. x = s.top(),s.pop();
  53. vis[x] = ,bel[x] = sum;
  54. }
  55. }
  56. }
  57.  
  58. void dijkstra(int s) {
  59. priority_queue<node>q;
  60. memset(dis,INF,sizeof(dis));
  61. memset(vis_new,,sizeof(vis_new));
  62. dis[s] = ;
  63. q.push((node){s,});
  64. while(!q.empty()) {
  65. node d = q.top();q.pop();
  66. int u = d.id;
  67. if(vis_new[u]) continue;
  68. vis_new[u] = ;
  69. for(int i = head_new[u]; ~i; i = e_new[i].nx) {
  70. int v = e_new[i].v;
  71. if(dis[v] > dis[u] + e_new[i].w) {
  72. dis[v] = dis[u] + e_new[i].w;
  73. q.push((node){v,dis[v]});
  74. }
  75. }
  76. }
  77. }
  78.  
  79. int main() {
  80. memset(head,-,sizeof(head));
  81. memset(head_new,-,sizeof(head_new));
  82. read(n),read(m);
  83. ,x,y,z; i <= m; ++ i) read(x),read(y),read(z),add(x,y,z);
  84. ; i <= n; ++ i) if(!dfn[i]) tarjan(i);
  85. ; i <= n; ++ i)
  86. for(int j = head[i]; ~j; j = e[j].nx) {
  87. int v = e[j].v;
  88. if(bel[i] != bel[v]) add_new(bel[i],bel[v],e[j].w);
  89. }
  90. dijkstra(bel[]);
  91. printf("%d\n",dis[bel[n]]);
  92. ;
  93. }

HDU3861

  1. /*
  2. tarjan缩点求最小路径覆盖集
  3. */
  4. #include <bits/stdc++.h>
  5. using namespace std;
  6. ;
  7. ;
  8. int t, n, m, num, num_new, sum, cnt, ans;
  9. int head[N], low[N], dfn[N], link[N], head_new[N], bel[N];
  10. bool vis[N], vis_new[N];
  11. struct node {
  12. int v, nx;
  13. } e[M], e_new[M];
  14.  
  15. template<class T>inline void read(T &x) {
  16. x = ; ; char ch = getchar();
  17. while (!isdigit(ch)) f |= (ch == '-'), ch = getchar();
  18. + ch - ', ch = getchar();
  19. x = f ? -x : x;
  20. return ;
  21. }
  22.  
  23. inline void add(int u, int v) {
  24. e[++num].nx = head[u], e[num].v = v, head[u] = num;
  25. }
  26.  
  27. inline void add_new(int u, int v) {
  28. e_new[++num_new].nx = head_new[u], e_new[num_new].v = v, head_new[u] = num_new;
  29. }
  30.  
  31. stack<int>s;
  32. void tarjan(int u) {
  33. dfn[u] = low[u] = ++cnt;
  34. vis[u] = ;
  35. s.push(u);
  36. for (int i = head[u]; ~i; i = e[i].nx) {
  37. int v = e[i].v;
  38. if (!dfn[v]) tarjan(v), low[u] = min(low[u], low[v]);
  39. else if (vis[v]) low[u] = min(low[u], dfn[v]);
  40. }
  41. if (low[u] == dfn[u]) {
  42. int x; sum++;
  43. while (x != u) {
  44. x = s.top(), s.pop();
  45. bel[x] = sum, vis[x] = ;
  46. }
  47. }
  48. }
  49.  
  50. bool dfs(int u) {
  51. for (int i = head_new[u]; ~i; i = e_new[i].nx) {
  52. int v = e_new[i].v;
  53. if (!vis_new[v]) {
  54. vis_new[v] = ;
  55. || dfs(link[v])) {
  56. link[v] = u;
  57. ;
  58. }
  59. }
  60. }
  61. ;
  62. }
  63.  
  64. int main(int argc, char const *argv[]) {
  65. read(t);
  66. while (t --) {
  67. read(n), read(m);
  68. memset(vis, , sizeof(vis));
  69. memset(low, , sizeof(low));
  70. memset(dfn, , sizeof(dfn));
  71. memset(bel, , sizeof(bel));
  72. memset(head, -, sizeof(head));
  73. memset(link, -, sizeof(link));
  74. memset(head_new, -, sizeof(head_new));
  75. memset(e, , sizeof(e));
  76. memset(e_new, , sizeof(e_new));
  77. ans = num = cnt = num_new = sum = ;
  78. , x, y; i <= m; ++ i) read(x), read(y), add(x, y);
  79. ; i <= n; ++i) if (!dfn[i]) tarjan(i);
  80. ; i <= n; ++i)
  81. for (int j = head[i]; ~j; j = e[j].nx) {
  82. int v = e[j].v;
  83. if (bel[i] != bel[v]) add_new(bel[i], bel[v]);
  84. }
  85. ; i <= sum; ++i) {
  86. memset(vis_new, , sizeof(vis_new));
  87. if (dfs(i)) ans ++;
  88. }
  89. printf("%d\n", sum - ans);
  90. }
  91. ;
  92. }

HDU 3861

Tarjan割点&&割边

割点

割点:在无向连通图中,删除一个顶点v及其相连的边后,原图从一个连通分量变成了两个或多个连通分量,则称顶点v为割点,同时也称关节点(Articulation Point)。

一个没有关节点的连通图称为重连通图(biconnected graph)。若在连通图上至少删去k 个顶点才能破坏图的连通性,则称此图的连通度为k。

some小例子

                          

割点为1的图(删去1和他的边图变成两部分)       连通度为2的图

首先明白几个概念

               

     (a)                    (b)

概念:

DFS树:用DFS对图进行遍历时,按照遍历次序的不同,我们可以得到一棵DFS搜索树,如图(b)所示。

树边:如(b)中的实线所示,可理解为在DFS过程中访问未访问节点时所经过的边

回边:如(b)中的虚线所示,可理解为在DFS过程中遇到已访问节点时所经过的边。

观察我们的DFS树(b),发现它有两种情况可能是割点:

1、对于一个根节点u,若有两个及以上的子树,说明u是割点

2、对于非叶子节点u(非根节点),若其子树均没有连向u祖先节点的回边,说明删掉u后,根节点与子树不再相连,则u是割点

对根节点很好处理,只要记录一下它的子树个数就好了,对于非叶节点呢

对!用low数组

我们用low记录节点u或u的子树通过非父子边追溯到最早的祖先节点

low[u] = min(low[u],low[v])(当(u,v)是树边)

low[u] = min(low[u],dfn[v])(当(u,v)是回边且v不等于u的父节点)

对于情况2,(u,v)是树边且当 low[v] ≥ dfn[u] 时,节点u是割点,因为v能回溯到的最早祖先不是u就是v,所以v并不能连到u的祖先上,当删去u时,v和u的祖先不相连,即u是割点

fa的意思就是从哪个点搜过来的,因为是无向图,可能还会搜到之前的点

代码实现:

  1. //cut标记当前点是否是割点
  2. void tarjan(int u,int fa) {
  3. dfn[u] = low[u] = ++cnt;
  4. ;//记录子树个数
  5. for(int i = head[u]; ~i; i = e[i].nx) {
  6. int v = e[i].v;
  7. if(!dfn[v]) {
  8. tarjan(v,u);
  9. low[u] = min(low[u],low[v]);
  10. ;//情况2
  11. if(u == fa) child ++;
  12. }
  13. low[u] = min(low[u],dfn[v]);
  14. }
  15. && u == fa) cut[u] = ;//情况1
  16. }

例题:

P3388 【模板】割点(割顶):

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. ;
  4. int n,m,num,cnt,tot;
  5. int dfn[N],low[N],head[N];
  6. bool cut[N];
  7. struct node {
  8. int v,nx;
  9. }e[N];
  10.  
  11. template<class T>inline void read(T &x) {
  12. x = ;;char ch = getchar();
  13. while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
  14. + ch - ',ch = getchar();
  15. x = f ? -x : x;
  16. return ;
  17. }
  18.  
  19. inline void add(int u,int v) {
  20. e[++num].nx = head[u],e[num].v = v,head[u] = num;
  21. }
  22.  
  23. void tarjan(int u,int fa) {
  24. dfn[u] = low[u] = ++cnt;
  25. ;
  26. for(int i = head[u]; ~i; i = e[i].nx) {
  27. int v = e[i].v;
  28. if(!dfn[v]) {
  29. tarjan(v,u);
  30. low[u] = min(low[u],low[v]);
  31. ;
  32. if(u == fa) child ++;
  33. }
  34. low[u] = min(low[u],dfn[v]);
  35. }
  36. && u == fa) cut[u] = ;
  37. }
  38.  
  39. int main() {
  40. memset(head,-,sizeof(head));
  41. read(n),read(m);
  42. ,x,y; i <= m; ++ i) read(x),read(y),add(x,y),add(y,x);
  43. ; i <= n; ++ i) if(!dfn[i]) tarjan(i,i);
  44. ; i <= n; ++ i) if(cut[i]) tot ++;
  45. printf("%d\n",tot);
  46. ; i <= n; ++ i) if(cut[i]) printf("%d ",i);
  47. ;
  48. }

P3388

P5058 [ZJOI2004]嗅探器

  1. //输出最小的割点编号
  2. #include <bits/stdc++.h>
  3. using namespace std;
  4. ;
  5. int n,m,k,num,cnt,x,y;
  6. int dfn[N],low[N],head[N];
  7. bool cut[N],vis[N];
  8. struct node {
  9. int nx,v;
  10. }e[N];
  11.  
  12. template<class T>inline void read(T &x) {
  13. x = ;;char ch = getchar();
  14. while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
  15. + ch - ',ch = getchar();
  16. x = f ? -x : x;
  17. return ;
  18. }
  19.  
  20. inline void add(int u,int v) {
  21. e[++num].nx = head[u],e[num].v = v,head[u] = num;
  22. }
  23.  
  24. void tarjan(int u,int fa) {
  25. dfn[u] = low[u] = ++cnt;
  26. ;
  27. for(int i = head[u]; ~i; i = e[i].nx) {
  28. int v = e[i].v;
  29. if(!dfn[v]) {
  30. tarjan(v,u);
  31. low[u] = min(low[u],low[v]);
  32. ;
  33. if(u == fa) child ++;
  34. }
  35. low[u] = min(low[u],dfn[v]);
  36. }
  37. ) cut[u] = ;
  38. }
  39.  
  40. void dfs(int u) {
  41. vis[u] = ;
  42. for(int i = head[u]; ~i; i = e[i].nx) {
  43. int v = e[i].v;
  44. if(!vis[v]) dfs(v);
  45. }
  46. }
  47.  
  48. int main() {
  49. memset(head,-,sizeof(head));
  50. read(n);
  51. while(scanf("%d%d",&x,&y)) {
  52. && y == ) break;
  53. add(x,y),add(y,x);
  54. }
  55. ; i <= n; ++ i) if(!dfn[i]) tarjan(i,i);
  56. read(x),read(y);
  57. ; i <= n; ++ i) {
  58. if(cut[i]) {
  59. memset(vis,,sizeof(vis));
  60. vis[i] = ;
  61. dfs(x);
  62. if(!vis[y]) {
  63. cout<<i;
  64. ;
  65. }
  66. }
  67. }
  68. cout<<"No solution";
  69. ;
  70. }

P5058

割边

割边的话其实也差不多甚至更简单

割边:也叫桥,当且仅当去掉该边之后的子图不连通

割边是用在无向图内的

割边不用记录子树情况,然后再改一句话把low[v] >= dfn[u]改为low[v] > dfn[u]

因为我们割点的时候是把当前节点u的所有的边都删去,而割边只能删一条边,显然,如果u,v之间有两条边的话,如果其中删掉一条边,另一条还和u相连,原图还保持连通

代码:

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. ;
  4. int n,m,num,cnt;
  5. int low[N],dfn[N],head[N];
  6. struct node {
  7. int v,nx,id;
  8. }e[N];
  9.  
  10. template<class T>inline void read(T &x) {
  11. x = ;;char ch = getchar();
  12. while(!isdigit(ch)) f |= (ch == '-'),ch = getchar();
  13. + ch - ',ch = getchar();
  14. x = f ? -x : x;
  15. return ;
  16. }
  17.  
  18. inline void add(int u,int v) {
  19. e[++num].nx = head[u],e[num].v = v,head[u] = num;
  20. }
  21.  
  22. void tarjan(int u,int fa) {
  23. dfn[u] = low[u] = ++cnt;
  24. for(int i = head[u]; ~i; i = e[i].nx) {
  25. int v = e[i].v;
  26. if(!dfn[v]) {
  27. tarjan(v,u);
  28. low[u] = min(low[u],low[v]);
  29. if(low[v] > dfn[u]) printf("%d -- %d is a Cutting edge\n",u,v);
  30. }
  31. else if(v != fa) low[u] = min(low[u],dfn[v]);
  32. }
  33. }
  34.  
  35. int main(int argc, char const *argv[]) {
  36. memset(head,-,sizeof(head));
  37. read(n),read(m);
  38. ,x,y; i <= m; ++ i) {
  39. read(x),read(y);
  40. add(x,y),add(y,x);
  41. }
  42. ; i <= n; ++ i) if(!dfn[i]) tarjan(i,i);
  43. ;
  44. }

点双连通分量&&边双连通分量

这东西我是现学现卖的,可能会有些地方不详细

既然我们要求点双连通分量和边双连通分量,那我们就要知道它们是什么

连通:无向图中,所有的点能相互到达

连通分量:相互连通的子图

点双连通分量:简单说,就是没有割点的子图,也就是删掉一个点后,图仍连通性

边双连通分量:简单说,就是没有割边的子图,也就是删掉一个边后,图仍连通性

如果先看具体定义,出门右转自己搜吧(其实是我懒得放百度链接,万一你们都用wiki呢)

这样说很抽象的话,还是来两张图好了

        

      (1)                      (2)       

如图(1)所示:1是割点,{1,2,4,3} 是一个点双连通分量,{1,5,7,6} 也是一个点双连通分量,不难发现,割点可能存在于多个点双连通分量里,而其他点只可能存在于一个点双连通分量里,同时,{1,2,3,4,5,6,7,8} 也是一个边双连通 分量;

如图(2)所示,1-4 是割边,在这个图内 {1,2,3} 和 {4,5,6} 分别组成了连个边双连通分量

求点双连通分量:

对于点双连通分量,在求割点的时候就可以顺便求出,但这是我们要建立一个,然后在搜索图的时候,让入栈,如果遇到 low[v] ≥ dfn[u] 即u是割点的时候,将 u - v 这条边之上的边全部弹出,这些边以及相邻的点就组成了一个点双连通分量,割点可能会属于多个双连通分量,其余的点只会属于一个双连通分量;

文字还是太无力,简单走一下流程:

一张简单图:

我们从1开始搜索,dfn[1] = 1,low[1] = 1

搜到2,2没被搜过(时间戳dfn),dfn[2] = 2,low[2] = 2,边 1-2 入栈,栈内元素:{1-2}

搜到3,3没被搜过,dfn[3] = 3,low[3] = 3,边2-3入栈,栈内元素:{1-2,2-3}

搜到4,4没被搜过,dfn[4] = 4,low[4] = 4,边3-4入栈,栈内元素:{1-2,2-3,3-4}

搜4,搜到了2,2被搜过,且4不是2搜过来的,low[4] = min(low[4],dnf[2]) = 2,2-4入栈,栈内元素:{1-2,2-3,3-4,4-2}

回到3,low[3] = min(low[3],low[4]) = 2

回到2,low[3] == low[2] 所以 2 是割点,弹栈,将边 2-3 以上的边全部弹出,将他们边连的顶点存入一个数组内,记为一个双连通分量

最后回到1,dfn[1] == low[1],弹栈,所以{1,2}也是一个点双连通分量

这样,我们就找到了点双联通分量 {1,2},{2,3,4};

这里同样还是用fa记录从那个点搜到了当前点

code:

  1. int dfn[N], low[N], head[N], bel_bcc[N], mn[N];
  2. bool cut[N];
  3.  
  4. struct edge {//从u到v的边
  5. int u, v;
  6. };
  7. struct node {//建图用
  8. int v, nx;
  9. } e[N];
  10.  
  11. stack<edge>s;//栈内存的是边,
  12. vector<int>bcc[N];
  13.  
  14. inline void add(int u, int v) {
  15. e[++num].nx = head[u], e[num].v = v, head[u] = num;
  16. }
  17.  
  18. void tarjan(int u, int fa) {
  19. dfn[u] = low[u] = ++cnt;
  20. ;
  21. for (int i = head[u]; ~i; i = e[i].nx) {
  22. int v = e[i].v;
  23. if (!dfn[v]) {
  24. s.push((edge) {u, v});
  25. child ++;
  26. tarjan(v, u);
  27. low[u] = min(low[u], low[v]);
  28. if (low[v] >= dfn[u]) {//是割点
  29. cut[u] = ;
  30. bcc_sum ++;//点双的个数++
  31. // bcc[bcc_sum].clear();
  32. ) {
  33. edge d = s.top(); s.pop();
  34. if (bel_bcc[d.u] != bcc_sum) bcc[bcc_sum].push_back(d.u),bel_bcc[d.u] = bcc_sum;//标记某点在这个电双块内
  35. if (bel_bcc[d.v] != bcc_sum) bcc[bcc_sum].push_back(d.v),bel_bcc[d.v] = bcc_sum;
  36. if (d.u == u && d.v == v) break;//弹栈一直到u-v
  37. }
  38. }
  39. } else if (dfn[v] < dfn[u] && v != fa) {//回边
  40. s.push((edge) {u, v});
  41. low[u] = min(low[u], dfn[v]);
  42. }
  43. }
  44. && child == ) cut[u] = ;//把根节点看做普通节点了,所以下面最后的特殊判断必需。
  45. }

例题:#1190 : 连通性·四

求边双连通分量:

边双联通分量可能简单些,

简单说,就是找到桥(割边)后,把桥删掉,剩下很多连通块,每一个连通块就是一个边双连通分量

因为把图中的桥删掉后,剩下的边都不是桥,既然不是桥,就不能把连通块分成多部分,所以剩下的连通块满足边双连通的定义,所以剩余的连通块都是边双联通

桥不属于任何一个边双连通分量,其余的边和每个顶点,都属于且只属于一个边连通分量

所以我们要做的就只有求桥,删边,染色DFS

code:

  1. ;
  2. ];
  3. void tarjan(int x, int in_edge) {
  4. dfn[x] = low[x] = ++cnt;
  5. for (int i = head[x]; ~i; i = e[i].nx) {
  6. int y = e[i].y;
  7. if (!dfn[y]) {
  8. tarjan(y, i);
  9. low[x] = min(low[x], low[y]);
  10. ] = ;
  11. }
  12. ))
  13. low[x] = min(low[x], dfn[y]);
  14. }
  15. }
  16. ;
  17. void dfs(int x, int color) {
  18. block[x] = color;
  19. ; i = e[i].nx) {
  20. int y = e[i].y;
  21. if (cut[i]) continue;
  22. if (!block[y]) dfs(y, color);
  23. }
  24. }
  25. void get_edccs() {
  26. ; i <= n; i++)
  27. if (!block[i])
  28. dfs(i, ++dcc);
  29. }

例题:

poj3352

Tarjan应用之——LCA

既然说到了Tarjan,那就不得不说一下它的应用,求LCA

Tarjan求LCA是一个O(n+m)的离线做法,查询是O(1)的

Tarjan求LCA其实就是深搜+并查集

先把算法流程摆上

1、任意选一个点为根节点

2、把当前节点u的father设为u,看与u相邻的点v,若v没被搜过,深搜v,深搜完后,把father[v]设成u

3、搜完一个点u后,开始判断与它相关的询问,如果另一个点被搜过,LCA就是另一个点的祖先

我相信看完了我的描述之后,你们还是不知道什么意思

那我们还是手模一下它的流程:

假设我们有这么一张图:

我们要查询2-4,5-6,4-7,6-8的LCA

从1开始搜索,fa[1] = 1,vis[1] = 1

搜到2,fa[2] = 2,vis[2] = 1

搜到3,fa[3] = 3,vis[3] = 1;

搜到4,fa[4] = 4,vis[4] = 1;

搜到6,fa[6] = 6,vis[6] = 1;

到此为止,我们就得到了这样的图 

发现6没有别的连边了,开始回溯

我们看到有一个关于6的询问6-8,发现vis[8] = 0,不管

回溯到4,fa[6]=4,发现和4有关的询问2-4,于是lca(2,4) = find(2) = 2 ,记录一下//find是找祖宗函数,并查集里常用

回溯到3,发现还有一条边,继续搜

搜到5,fa[5] = 5,vis[5] = 1

5没有别的连边,找和5相关的询问5-6,lca(5,6) = find(6) = 3,记录一下

回溯到3,fa[5] = 3

回溯到2,fa[3] = 2

回溯到1,fa[2] = 1

这样左边的就搜完了

得到

回溯到1

继续搜,搜到7,fa[7] = 7,vis[7] = 1

搜到8,fa[8] = 8,vis[8] = 1;

8没有其他的连边,发现有一个和8相关的询问6-8,lca(6,8) = find(6) = 1

回溯到7,fa[8] = 7,发现一个和7相关的询问4-7,lca(4,7) = find(4) = 1

回溯到1,fa[7] = 1

到此为止,我们已经把整棵树dfs了一遍,也处理处了所有的询问的结果

最后直接输出就好了

看代码:

P3379 【模板】最近公共祖先(LCA)

  1. //e是记录原图,_e是记录询问
  2. //lca记录的是第i组询问的结果
  3. #include <bits/stdc++.h>
  4. using namespace std;
  5. ;
  6. int n, m, k, num, _num;
  7. int fa[N], head[N], _head[N];
  8. bool vis[N];
  9. struct node {
  10. int v, nx, lca;
  11. } e[N], _e[N];
  12.  
  13. template<class T>inline void read(T &x) {
  14. x = ; ; char ch = getchar();
  15. while (!isdigit(ch)) f |= (ch == '-'), ch = getchar();
  16. + ch - ', ch = getchar();
  17. x = f ? -x : x;
  18. return ;
  19. }
  20.  
  21. inline void add(int u, int v) {
  22. e[++num].nx = head[u], e[num].v = v, head[u] = num;
  23. }
  24.  
  25. inline void _add(int u, int v) {
  26. _e[++_num].nx = _head[u], _e[_num].v = v, _head[u] = _num;
  27. }
  28.  
  29. int find(int x) {
  30. return x == fa[x] ? x : fa[x] = find(fa[x]);
  31. }
  32.  
  33. void tarjan(int u) {
  34. fa[u] = u, vis[u] = ;
  35. for (int i = head[u]; ~i; i = e[i].nx) {
  36. int v = e[i].v;
  37. if (!vis[v]) {
  38. tarjan(v);
  39. fa[v] = u;
  40. }
  41. }
  42. for (int i = _head[u]; ~i; i = _e[i].nx) {
  43. int v = _e[i].v;
  44. if (vis[v]) {
  45. _e[i].lca = find(v);
  46. ) _e[i + ].lca = _e[i].lca;//因为是加的双向边,奇数边是从父到子加的,偶数边是从子到父加的,两者相同
  47. ].lca = _e[i].lca;
  48. }
  49. }
  50. }
  51.  
  52. int main(int argc, char const *argv[]) {
  53. memset(head, -, sizeof(head));
  54. memset(_head, -, sizeof(_head));
  55. read(n), read(m), read(k);
  56. , x, y; i < n; ++ i) read(x), read(y), add(x, y), add(y, x);
  57. , x, y; i <= m; ++ i) read(x), read(y), _add(x, y), _add(y, x);
  58. tarjan(k);
  59. ; i <= m; ++ i) printf(].lca);
  60. ;
  61. }

例题:

P2912 [USACO08OCT]牧场散步Pasture Walking

  1. //dis[i]表示到i的距离
  2. //这一类题,dis直接在Tarjan中计算就好了
  3. #include <bits/stdc++.h>
  4. using namespace std;
  5. ;
  6. int n, m, num, _num;
  7. int head[N], _head[N], fa[N], dis[N];
  8. bool vis[N];
  9. struct node {
  10. int v, nx, w, lca, ans;
  11. } e[N], _e[N];
  12.  
  13. template<class T>inline void read(T &x) {
  14. x = ; ; char ch = getchar();
  15. while (!isdigit(ch)) f |= (ch == '-'), ch = getchar();
  16. + ch - ', ch = getchar();
  17. x = f ? -x : x;
  18. return ;
  19. }
  20.  
  21. inline void add(int u, int v, int w) {
  22. e[++num].nx = head[u], e[num].v = v, e[num].w = w, head[u] = num;
  23. }
  24.  
  25. inline void _add(int u, int v) {
  26. _e[++_num].nx = _head[u], _e[_num].v = v, _head[u] = _num;
  27. }
  28.  
  29. int find(int x) {
  30. return x == fa[x] ? x : fa[x] = find(fa[x]);
  31. }
  32.  
  33. void tarjan(int u) {
  34. fa[u] = u, vis[u] = ;
  35. for (int i = head[u]; ~i; i = e[i].nx) {
  36. int v = e[i].v;
  37. if (!vis[v]) {
  38. dis[v] = dis[u] + e[i].w;
  39. tarjan(v);
  40. fa[v] = u;
  41. }
  42. }
  43. for (int i = _head[u]; ~i; i = _e[i].nx) {
  44. int v = _e[i].v;
  45. if (vis[v]) {
  46. _e[i].lca = find(v);
  47. _e[i].ans = dis[u] + dis[v] - dis[_e[i].lca] * ;
  48. ) _e[i + ].lca = _e[i].lca, _e[i + ].ans = _e[i].ans;
  49. ].lca = _e[i].lca, _e[i - ].ans = _e[i].ans;
  50. }
  51. }
  52. }
  53.  
  54. int main(int argc, char const *argv[]) {
  55. memset(head, -, sizeof(head));
  56. memset(_head, -, sizeof(_head));
  57. read(n), read(m);
  58. , x, y, z; i < n; ++ i) read(x), read(y), read(z), add(x, y, z), add(y, x, z);
  59. , x, y; i <= m; ++ i) read(x), read(y), _add(x, y), _add(y, x);
  60. tarjan();
  61. ; i <= m; ++ i) printf(].ans);
  62. ;
  63. }

P2912

一般的LCA都可以做的吧。。(我做题少,而且都是用倍增做的,如有什么不可以题的话还请告知我一下)

Tarjan总结(缩点+割点(边)+双联通+LCA+相关模板)的更多相关文章

  1. Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载)

    Tarjan算法应用 (割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)问题)(转载) 转载自:http://hi.baidu.com/lydrainbowcat/blog/item/2 ...

  2. Tarjan的缩点&&割点概述

    What is Tarjan? Tarjan,是一种用来解决图的联通性的一种有效途径,它的一般俗称叫做:缩点.我们首先来设想一下: 如果我们有一个图,其中A,B,C构成一个环,那么我们在某种条件下,如 ...

  3. hdu 2460 poj 3694 (双联通+LCA)

    在给出的两个点上加一条边,求剩下桥的数量,,不会LCA在线,就用了最普通的,先Tarjan双联通缩点,然后将缩完的图建成一棵树,树的所有边就是桥了,如果在任意两点间加一条边的话,那么从两点到最近公共祖 ...

  4. Tarjan应用:求割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)【转】【修改】

    一.基本概念: 1.割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点. 2.割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成 ...

  5. (转)Tarjan应用:求割点/桥/缩点/强连通分量/双连通分量/LCA(最近公共祖先)

    基本概念: 1.割点:若删掉某点后,原连通图分裂为多个子图,则称该点为割点. 2.割点集合:在一个无向连通图中,如果有一个顶点集合,删除这个顶点集合,以及这个集合中所有顶点相关联的边以后,原图变成多个 ...

  6. 无向连通图求割点(tarjan算法去掉改割点剩下的联通分量数目)

    poj2117 Electricity Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 3603   Accepted: 12 ...

  7. POJ 3694 Network 无向图双联通+LCA

    一开始题目没看清楚,以为是增加那条边后还有多少桥,所以就当做是无向图tarjan缩点后建树,然后求u,v的最近公共祖先,一直wa. 后来再看题目后才发现边放上去后不会拿下来了,即增加i条边后桥的数量. ...

  8. HDU4612 Warm up —— 边双联通分量 + 重边 + 缩点 + 树上最长路

    题目链接:http://acm.split.hdu.edu.cn/showproblem.php?pid=4612 Warm up Time Limit: 10000/5000 MS (Java/Ot ...

  9. POJ3177 Redundant Paths —— 边双联通分量 + 缩点

    题目链接:http://poj.org/problem?id=3177 Redundant Paths Time Limit: 1000MS   Memory Limit: 65536K Total ...

随机推荐

  1. SDN 实验室学生们

    SDN实验室系列:https://edu.cnblogs.com/campus/fzu/SdnLab --研究生及本科生们的博客及GitHub链接.欢迎各位大佬招募. 研究生 2018级 姓名 博客地 ...

  2. Day12 Python基础之生成器、迭代器(高级函数)(十)

    https://www.cnblogs.com/yuanchenqi/articles/5769491.html 1. 列表生成式 我现在有个需求,看列表[0, 1, 2, 3, 4, 5, 6, 7 ...

  3. Linux awk使用方法~~整理

    目录 awk行处理方式 awk命令格式 命令行格式 脚本格式 命令行格式——基本格式 awk内置变量 awk内置函数 测试数据 awk变量和函数使用实例 逻辑判断式 扩展格式 BEGIN 和 END ...

  4. 我的第一个Go web程序 纪念一下

    参考Go web编程,很简单的程序: 大致的步骤: 绑定ip和端口 绑定对应的处理器或者处理器函数,有下面两种选择,选择一种即可监听ip及端口 处理器: 定义一个struct结构体 然后让这个结构体实 ...

  5. shell脚本--php执行普通shell命令

    这里只演示一些普通的shell命令,一些需要root用户权限执行的命令,请参考:php以root权限执行shell命令 php执行shell命令,可以使用下面几个函数: string system ( ...

  6. pip Read timed out 和 pip 源

    解决方法,设置超时时间 pip --default-timeout=100 install -U Pillow 安装时指定源(--index-url) #例如安装scipy时使用豆瓣的源 pip in ...

  7. Nginx三部曲(1)基础

    我们会告诉你 Nginx 是如何工作的,其背后的概念有哪些,以及如何优化它以提升应用程序的性能.还会告诉你如何安装,如何启动.运行. 这个教程包括三节: 基础概念——你可以了解命令(directive ...

  8. ES7的新特性

    ES7的新特性 ES7 特性: 1.Array.prototype.includes2.Exponentiation Operator(求幂运算) 一,Array.prototype.includes ...

  9. 在linux和本地系统之间进行数据传输的简单方法--lrzsz

    lrzsz是一款在linux里可代替ftp上传和下载的程序. >>提君博客原创  http://www.cnblogs.com/tijun/  << 提君博客原创 安装和使用非 ...

  10. Flutter之Container详解

    1 基本内容1.1 继续关系Object > Diagnosticable > DiagnosticableTree > Widget > StatelessWidget &g ...