边的分类

有向图

有向图边分为四类: 树边, 前向边, 返祖边(后向边), 横叉边.

上图:

判定

对图进行dfs, 不考虑已经遍历过的点, 得到dfs序 \(dfn_i\).

在dfs过程中, 记录当前dfs栈. 对于边\((u,v)\),

  1. 树边: \(vis_v=0\);
  2. 前向边: \(vis_v=1\) 且 \(dfn_v > dfn_u\);
  3. 返祖边: \(vis_v=1\) 且 \(dfn_v < dfn_u\), 且 \(v\) 在当前栈内;
  4. 横叉边: \(vis_v=1\) 且 \(dfn_v < dfn_u\), 且 \(v\) 不在当前栈内.

无向图

如上图, 边仅分为两种:

  1. 树边: \(vis_v=0\) ;
  2. 前向边/返祖边, 这两个其实是同一条边. 可以通过 dfs 序区分.

有向图的强连通分量 && 缩点 (Tarjan)

Tarjan算法寻找有向图的强连通分量 – Miskcoo's Space

简介

Tarjan 强连通分量算法可以找出图的所有强连通分量, 并为每个点标记所在的强连通分量.

定义:

  • \(dfn_i\) : \(i\) 节点的dfs序;
  • \(low_i\) : \(i\) 节点通过最多一条返祖边能到达的最小dfs序.
  • 栈, 维护:
    1. 当前节点到根的链;
    2. 在这条链中节点所在的强连通分量中, 且之前遍历过的点. (是在这个节点的子树中的一些节点)
  • \(vi_i\): 表示节点是否在栈内.

应用

缩点.

tarjan求出的强连通分量标号为逆拓扑序.

代码

int dfn[nsz],low[nsz],pd=0,scc[nsz],ps=0;
int stk[nsz],top=0,vi[nsz];
// get scc
void tar(int p){
dfn[p]=low[p]=++pd;
stk[++top]=p,vi[p]=1;
for(int i=g1.hd[p],v;i;i=g1.edge[i].pr){
v=g1.edge[i].t;
if(dfn[v]==0){
tar(v);
low[p]=min(low[p],low[v]);
}
else if(vi[v])low[p]=min(low[p],dfn[v]); // 返祖边; 横叉边不更新
}
if(low[p]==dfn[p]){
++ps;
do{
vi[stk[top]]=0;
scc[stk[top]]=ps;
}while(stk[top--]!=p);
}
}
//缩点
void sol(){
rep(i,1,n)if(scc[i]==0)tar(i);
rep(i,1,n){
for(int j=g1.hd[i],v;j;j=g1.edge[j].pr){
v=g1.edge[j].t;
if(scc[i]!=scc[v])g2.adde(scc[i],scc[v]);
}
}
}

其他

事实上, 将 \(low_i\) 定义为 "\(i\) 节点通过任意条返祖边能到达的最小dfs序" 也是合法的, 即将

        else if(vi[v])low[p]=min(low[p],dfn[v]);

改为

        else if(vi[v])low[p]=min(low[p],low[v]);

不难发现这两个做法是等价的; 但在求双连通分量时这么写是错误的.

无向图的边双连通分量,割边,缩点

点双连通分量为对点的划分.

求边双/缩点代码和强连通分量几乎相同.

栈中维护的是:

  1. 当前点到根的路径
  2. 当前点所在点双中的点
//由于可能有重边, 搜索时传递的应为e(fa,p)这条边, 而非父亲这个点
int dfn[nsz],low[nsz],pd=0,ebcc[nsz],pb=0;
int stk[nsz],top=0,vi[nsz];
void tar(int p,int efa){
dfn[p]=low[p]=++pd;
stk[++top]=p,vi[p]=1;
forg(p,i,v){
if(i==(efa^1))continue;
if(dfn[v]==0){
tar(v,i); //warning
low[p]=min(low[p],low[v]);
}
else low[p]=min(low[p],dfn[v]);
}
if(dfn[p]==low[p]){ //efa is cut edge
++pb;
do{
ebcc[stk[top]]=pb;
vi[stk[top]]=0;
}while(stk[top--]!=p);
}
}
int in[nsz];
int sol(){
rep(i,1,n)if(dfn[i]==0)tar(i,0);
rep(i,1,n){
forg(i,j,v){
if(ebcc[v]!=ebcc[i])++in[ebcc[v]]; //a cut edge
}
}
}

无向图的点双连通分量,割点,缩点

点双连通分量为对边的划分, 因此栈中存边, 而非点.

割点 \(\iff\) 属于多个点双的点.

所有点双和割点形成一棵树结构.

缩点: 在新图中对每个割点和点双各建一个点, 将每个割点连向所在的点双.

代码较长, 但比较容易理解.

//cf962f
//无自环
int dfn[nsz],pd=0,low[nsz],iscut[nsz],curbcc[nsz],pbcc=0;
int bccsz[nsz];
set<int> inbcc[nsz];
pair<int,int> stk[msz];
int top=0;
void color(int p,int pbcc){
if(curbcc[p]!=pbcc){
inbcc[p].insert(pbcc);
curbcc[p]=pbcc;
++bccsz[pbcc];
}
}
//could not find
void tarj(int p,int fe){
dfn[p]=low[p]=++pd;
for(int i=hd[p],v;i;i=edge[i].pr){
if(i==fe)continue;
v=edge[i].t;
if(dfn[v]==0){
stk[++top]=make_pair(p,v);
tarj(v,i^1);
low[p]=min(low[p],low[v]);
if(low[v]>=dfn[p]){//点双
iscut[p]=1,++pbcc;
int x,y;
do{
x=stk[top].first,y=stk[top].second,--top;
color(y,pbcc);
}while(!(x==p&&y==v));
color(p,pbcc); //一条边属于一个点双
}
}
else if(dfn[v]<dfn[p]){//返祖边
low[p]=min(low[p],dfn[v]);
stk[++top]=make_pair(p,v);
}
}
if(fe==0){
if(hd[p]==0)color(p,++pbcc); //只有一个点的图
if(edge[hd[p]].pr==0)iscut[p]=0;//度数<=1的根不是割点
}
}
//debug
rep(i,1,n){
printf("p=%d iscut=%d\n",i,iscut[i]);
for(int v:inbcc[i])printf("%d ",v);
printf("\n");
}
//3 2 1 2 2 3
//p=1 iscut=0
//2
//p=2 iscut=1
//1 2
//p=3 iscut=0
//1 //6 5
//1 6 6 2 6 3 2 4 2 5
//p=1 iscut=0
//5
//p=2 iscut=1
//2 3 4
//p=3 iscut=0
//1
//p=4 iscut=0
//3
//p=5 iscut=0
//2
//p=6 iscut=1
//1 4 5 //5 6
//1 2 1 3 3 4 5 3 5 1 3 5
//p=1 iscut=1
//2 3
//p=2 iscut=0
//3
//p=3 iscut=1
//1 2
//p=4 iscut=0
//1
//p=5 iscut=0
//2

最新文章

  1. 使用MAT(Memory Analyzer Tool)工具分析dump文件--转
  2. java 创建string对象机制 字符串缓冲池 字符串拼接机制
  3. Android带侧滑菜单和ToolBar的BaseActivity
  4. JS判断终端设备跳转PC端、移动端相应的URL
  5. iOS 中使用Block时需要注意的retain circle
  6. having 子句
  7. js生成动态日历
  8. PHP后台传值
  9. 线程间同步之 semaphore(信号量)
  10. Flutter 即学即用系列博客——09 EventChannel 实现原生与 Flutter 通信(一)
  11. Docker-服务(4)
  12. Linux中环境变量文件
  13. php四排序-选择排序
  14. 基于Vue、Bootstrap的Tab形式的进度展示
  15. chrom 自带截屏用法
  16. 数组比较大小的几种方法及math是方法
  17. eclipse插件常用网址链接
  18. Django基础教程
  19. poj 1966(顶点连通度)
  20. bzoj 3879: SvT

热门文章

  1. 页面优化,谈谈重绘(repaint)和回流(reflow)
  2. Python3+Selenium2完整的自动化测试实现之旅(一):自动化测试环境搭建
  3. rabbitmq高级消息队列
  4. [Javascript] js的类和对象
  5. .net后台防止API接口被重复请求
  6. Servlet--创建和配置Servlet
  7. 详解块级格式化上下文(BFC)
  8. 为Jekyll+GitHub Pages添加全文搜索功能
  9. 【20190220】HTTP-知识点整理:TCP/IP与HTTP
  10. ButterKnife的使用详解