Luogu P3387

强连通分量的定义如下:

有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通(strongly connected)。如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量(strongly connected components)。

来源于百度百科

我本人的理解:有向图内的一个不能再拓展得更大的强连通子图叫做这个有向图的一个强连通分量(也可以说是一个环)

注意:单独的一个孤立的点也会是一个强连通分量

求出强连通分量以后有什么用呢?

很显然,我们可以把整个强连通分量作为单独的一个点,权值按照题目要求取(在这题就是取所有点权的总和),这样就可以让这一个有向有环图转化成一个有向无环图。

Tarjan算法

Tarjan算法(在这里指Tarjan对于强连通分量提出的算法)就是用于求出一个有向图内的所有强连通分量的有效算法。

基本思想就是利用DFS往下搜索,标记顺序,如果找到返回祖先的一条边,则说明会构成一个环(强连通分量)。

这里要引入几个Tarjan算法必备的数组

数组名 作用
dfn[i] 用于记录节点i的dfs序
stk[i] 一个栈,用于记录当前搜索的这一条链上的节点
low[i] 用于记录节点i能访问到的节点中最小的dfs序 ,也就是最上层的祖先

关键点:如果dfn[i]==low[i],意味着在节点i的子树中没有任何的节点可以访问到节点i的祖先,说明节点i与仍然在栈内子节点(必要条件)构成了一个强连通分量

不在栈内的子节点无法与节点i构成强连通分量,原因是不在栈内则说明它本身已经作为一个强连通分量被弹出栈了。

  1. void tarjan(int now)
  2. {
  3. dfn[now]=++tim;//记录dfs序
  4. low[now]=tim;//当前能访问到dfs序最小的点就是自己
  5. stk[++cnt]=now;
  6. vis[now]=true;
  7. for (int i=head[now];i;i=e[i].nxt)
  8. {
  9. int to=e[i].to;
  10. if (!dfn[to])
  11. {
  12. tarjan(to);
  13. low[now]=min(low[now],low[to]);
  14. //如果该点没被遍历过,那么就进行遍历。
  15. }
  16. else
  17. {
  18. if (vis[to]) low[now]=min(low[now],dfn[to]);
  19. //必须判断是否在栈中。只有在同时在栈内的点才有可能构成强连通分量。
  20. }
  21. }
  22. if (low[now]==dfn[now])
  23. {
  24. tot++;//强连通分量的编号
  25. while (stk[cnt]!=now)
  26. {
  27. scc[stk[cnt]]=tot;
  28. val[tot]+=a[stk[cnt]];
  29. vis[stk[cnt]]=false;
  30. cnt--;
  31. }
  32. scc[stk[cnt]]=tot;
  33. val[tot]+=a[stk[cnt]];
  34. vis[stk[cnt]]=false;
  35. cnt--;
  36. //将栈中比u后进入的点和u本身出栈,这些点构成一个强联通分量,打上标记
  37. }
  38. }

结合代码进行理解。

拓扑排序和缩点操作

对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列,简称拓扑序列。简单的说,由某个集合上的一个偏序得到该集合上的一个全序,这个操作称之为拓扑排序。

来源于百度百科

我个人的理解:对有向无环图中所有节点排序形成一个合法的访问次序。

做一个比喻:吃饭之前要端盘子,端盘子之前要炒菜——那么对于这三件事的拓扑次序就是炒菜→端盘子→吃饭。

那么具体应该如何处理呢?

事实上有两种实现方法,但是我个人暂时只会一种。

利用队列的方式,把所有入度为0的点入队,然后把这几个点对其他点入度的贡献删除,然后再把入度为0的点入队,直到排序完成为止。

完成拓扑排序后就可以利用拓扑次序进行动态规划了。

完整代码如下:

  1. #include<cstdio>
  2. #include<queue>
  3. using namespace std;
  4. queue<int> que;
  5. struct data
  6. {
  7. int sta,to,nxt;
  8. }e[500005],ine[500005],oute[500005];
  9. int dfn[100005],tim,low[100005],stk[100005],cnt,cnti,cnto,head[100005],inhead[100005],outhead[100005];
  10. bool vis[10005];
  11. int tot,scc[100005],val[100005],a[100005],n,in[100005],order[100005],m,u,v,f[100005],ans;
  12. void tarjan(int now)
  13. {
  14. dfn[now]=++tim;//记录dfs序
  15. low[now]=tim;//当前能访问到dfs序最小的点就是自己
  16. stk[++cnt]=now;
  17. vis[now]=true;
  18. for (int i=head[now];i;i=e[i].nxt)
  19. {
  20. int to=e[i].to;
  21. if (!dfn[to])
  22. {
  23. tarjan(to);
  24. low[now]=min(low[now],low[to]);
  25. //如果该点没被遍历过,那么就进行遍历。
  26. }
  27. else
  28. {
  29. if (vis[to]) low[now]=min(low[now],dfn[to]);
  30. //必须判断是否在栈中。只有在同时在栈内的点才有可能构成强连通分量。
  31. }
  32. }
  33. if (low[now]==dfn[now])
  34. {
  35. tot++;//强连通分量的编号
  36. while (stk[cnt]!=now)
  37. {
  38. scc[stk[cnt]]=tot;
  39. val[tot]+=a[stk[cnt]];
  40. vis[stk[cnt]]=false;
  41. cnt--;
  42. }
  43. scc[stk[cnt]]=tot;
  44. val[tot]+=a[stk[cnt]];
  45. vis[stk[cnt]]=false;
  46. cnt--;
  47. //将栈中比u后进入的点和u本身出栈,这些点构成一个强联通分量,打上标记
  48. }
  49. }
  50. void topo()//拓扑排序
  51. {
  52. cnt=0,cnti=0,cnto=0;
  53. for (int i=1;i<=n;i++)
  54. {
  55. for (int j=head[i];j;j=e[j].nxt)
  56. {
  57. if (scc[i]!=scc[e[j].to])
  58. {
  59. oute[++cnto].to=scc[e[j].to];
  60. oute[cnto].nxt=outhead[scc[i]];
  61. outhead[scc[i]]=cnto;
  62. in[scc[e[j].to]]++;
  63. ine[++cnti].sta=scc[i];
  64. ine[cnti].nxt=inhead[scc[e[j].to]];
  65. inhead[scc[e[j].to]]=cnti;
  66. //out前缀的变量是出边的记录
  67. //in前缀的变量是入边的记录,使用了一种另类的链式前向星
  68. }
  69. }
  70. }
  71. for (int i=1;i<=tot;i++)
  72. if (in[i]==0) que.push(i);//入度为零则入队
  73. cnt=0;
  74. while (!que.empty())
  75. {
  76. int u=que.front();
  77. que.pop();
  78. order[++cnt]=u;//记录顺序
  79. for (int i=outhead[u];i;i=oute[i].nxt)
  80. {
  81. int v=oute[i].to;
  82. in[v]--;
  83. if (in[v]==0) que.push(v);
  84. }
  85. }
  86. }
  87. int main()
  88. {
  89. scanf("%d%d",&n,&m);
  90. for (int i=1;i<=n;i++)
  91. scanf("%d",&a[i]);
  92. for (int i=1;i<=m;i++)
  93. {
  94. scanf("%d%d",&u,&v);
  95. e[i].to=v;
  96. e[i].nxt=head[u];
  97. head[u]=i;
  98. //链式前向星存原图
  99. }
  100. tim=0;
  101. for (int i=1;i<=n;i++) if (!dfn[i]) tarjan(i);
  102. //如果没有被遍历过的点要继续遍历。
  103. topo();
  104. for (int i=1;i<=tot;i++)
  105. {
  106. f[order[i]]=val[order[i]];
  107. for (int j=inhead[order[i]];j;j=ine[j].nxt)
  108. f[order[i]]=max(f[order[i]],f[ine[j].sta]+val[order[i]]);
  109. //很容易的一个动态规划
  110. }
  111. for (int i=1;i<=tot;i++) ans=max(f[i],ans);//统计答案
  112. printf("%d",ans);
  113. return 0;
  114. }

【Luogu P3387】缩点模板(强连通分量Tarjan&拓扑排序)的更多相关文章

  1. 【BZOJ2330】糖果(差分约束系统,强连通分量,拓扑排序)

    题意: 幼儿园里有N个小朋友,lxhgww老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果.但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖 ...

  2. 强连通分量(tarjan求强连通分量)

    双DFS方法就是正dfs扫一遍,然后将边反向dfs扫一遍.<挑战程序设计>上有说明. 双dfs代码: #include <iostream> #include <cstd ...

  3. 【bzoj1093】[ZJOI2007]最大半连通子图 Tarjan+拓扑排序+dp

    题目描述 一个有向图G=(V,E)称为半连通的(Semi-Connected),如果满足:对于u,v∈V,满足u→v或v→u,即对于图中任意两点u,v,存在一条u到v的有向路径或者从v到u的有向路径. ...

  4. 【bzoj5017】[Snoi2017]炸弹 线段树优化建图+Tarjan+拓扑排序

    题目描述 在一条直线上有 N 个炸弹,每个炸弹的坐标是 Xi,爆炸半径是 Ri,当一个炸弹爆炸时,如果另一个炸弹所在位置 Xj 满足:  Xi−Ri≤Xj≤Xi+Ri,那么,该炸弹也会被引爆.  现在 ...

  5. 强连通分量(Tarjan)模板

    贴模板,备忘. 模板1: #include<iostream> #include<cstring> #include<cmath> #include<cstd ...

  6. 强连通分量tarjan缩点——POJ2186 Popular Cows

    这里的Tarjan是基于DFS,用于求有向图的强联通分量. 运用了一个点dfn时间戳和low的关系巧妙地判断出一个强联通分量,从而实现一次DFS即可求出所有的强联通分量. §有向图中, u可达v不一定 ...

  7. POJ 1236 Network of Schools(强连通分量/Tarjan缩点)

    传送门 Description A number of schools are connected to a computer network. Agreements have been develo ...

  8. 强连通分量Tarjan模板

    #include<iostream> #include<stdio.h> #include<string.h> #include<stack> #inc ...

  9. POJ1236_A - Network of Schools _强连通分量::Tarjan算法

    Time Limit: 1000MS   Memory Limit: 10000K Description A number of schools are connected to a compute ...

随机推荐

  1. 如何在Idea中编译构建Spring Framework 5.x

    如何在Idea中编译构建Spring Framework 5.x 安装配置Gradle(略) 下载源码:git clone https://github.com/spring-projects/spr ...

  2. Spring MVC(3)Spring MVC 高级应用

    一.Spring MVC 的数据转换和格式化 前面的应用,都只是用HandlerAdapter去执行处理器. 处理器和控制器不是一个概念,处理器是在控制器功能的基础上加上了一层包装,有了这层包装,在H ...

  3. 数据库(一)--通过django创建数据库表并填充数据

    django是不能创建数据库的,只能够创建数据库表,因此,我们在连接数据库的时候要先建立一个数据库. 在models.py中 from django.db import models class Pu ...

  4. DRF之注册器、响应器、分页器

    一.url注册器 通过DRF的视图组件,数据接口逻辑被我们优化到最剩下一个类,接下来,我们使用DRF的url控制器来帮助我们自动生成url,使用步骤如下: 第一步:导入模块 1 from rest_f ...

  5. JAVA中的NIO (New IO)

    简介 标准的IO是基于字节流和字符流进行操作的,而JAVA中的NIO是基于Channel和Buffer进行操作的. 传统IO graph TB; 字节流 --> InputStream; 字节流 ...

  6. win10+MinGw+ffmpeg 编译

    一.安装MinGw+msys 下载 mingw-get-setup.exe 并安装,安装完成会弹出以下界面. 选中红色框几个选项,点击Installation->Apply Changes 进行 ...

  7. Centos6.5 忘记密码解决方法

    问题 原因  : 太久没用centos了  忘记密码了 很尴尬 快照也没说明密码.... 1.重启 centos 在开机启动的时候快速按键盘上的“E”键 或者“ESC”键(如果做不到精准快速可以在启动 ...

  8. pymssql的Connection相关特性浅析

    关于Python的pymssql模块,之前研究时总结了"pymssql默认关闭自动模式开启事务行为浅析"这篇博客,但是在测试过程中又发现了几个问题,下面对这些问题做一些浅析,如有不 ...

  9. Mybatis:CRUD操作

    提示: Mapper配置文件的命名空间为对应接口包名+接口名字,这个经常会忘记和搞错的!! select标签 在接口中编写三个查询方法 //获取全部用户List<User> selectU ...

  10. Laravel + Vue + Element 考勤应用 - 人力资源系统

    项目地址 Bee 介绍 Bee 是人力资源系统中的考勤应用,主要功能用于员工申请假单.Bee具有较高的性能.扩展性等,其中包括前后端分离.插拔式的规则验证(验证器).数据过滤(装饰器).消息队列等,后 ...