<更新提示>

<第一次更新>


<正文>

无向图的双连通分量

定义:若一张无向连通图不存在割点,则称它为"点双连通图"。若一张无向连通图不存在割边,则称它为"边双连通图"。

无向图图的极大点双连通子图被称为"点双连通分量",记为"\(v-DCC\)"。无向图图的极大边双连通子图被称为"边双连通分量",记为"\(e-DCC\)"。

没错,万能的图论连通性算法\(Tarjan\)又来了。

预备知识

时间戳

图在深度优先遍历的过程中,按照每一个节点第一次被访问到的顺序给\(N\)个节点\(1-N\)的标记,称为时间戳,记为\(dfn_x\)。

追溯值

设节点\(x\)可以通过搜索树以外的边回到祖先,那么它能回到祖先的最小时间戳称为节点\(x\)的追溯值,记为\(low_x\)。当\(x\)没有除搜索树以外的边时,\(low_x=x\)。


如何求解见『Tarjan算法 无向图的割点与割边』

Tarjan 算法

著名的\(Tarjan\)算法可以在线性时间内求解无向图的双连通分量。

边双连通分量

核心概念:没有割边的无向连通图。

注意到,割边只会把图分成两部分,对图中的点没有影响。那么有一个显而易见的求法就是:利用\(Tarjan\)算法求解无向图的割边,并将割边去除,得到的各个连通块即为边双连通分量。

\(Tarjan\)算法求解割边详见『Tarjan算法 无向图的割点与割边』

\(Code:\)

inline void Tarjan(int x,int inedge)
{
dfn[x]=low[x]=++cnt;
for(int i=Last[x];i;i=e[i].next)
{
int y=e[i].ver;
if(!dfn[y])
{
Tarjan(y,i);
low[x]=min(low[x],low[y]);
if(low[y]>dfn[x])e[i].flag=e[i^1].flag=true;
}
else if(i!=(inedge^1))low[x]=min(low[x],dfn[y]);
}
}
//先求解出无向图的割边
inline void dfs(int x)
{
con[x]=tot;
for(int i=Last[x];i;i=e[i].next)
{
if(e[i].flag)continue;//如果是割边,则不能经过
int y=e[i].ver;
if(!con[y])dfs(y);
}
}
//利用dfs遍历每一个边双联通分量
inline void colored(void)
{
for(int i=1;i<=n;i++)
if(!con[i])++tot,dfs(i);
}
//将每一个边双联通分量内的节点进行标号,类似于染色的思想

点双连通分量

核心概念:没有割点的无向连通图。

点双连通分量的求解就没有边双连通分量那么简单了,去掉割点显然是不行的,我们可以看如下例子。



它的割点是\(2\),但是,他有三个点双连通分量:\(\{1,2\},\{2,3\},\{2,3,4\}\)。

这里给出定理,无向连通图是"点双连通图",当且仅当满足下列两个条件之一:

  • 图的顶点不超过\(2\)个。
  • 图中任意两个点都同时包含在一个简单环中。"简单环"指的是不相交的环。

点双连通分量的求法:

  • 若某个点为“孤立点”,这个点肯定是点双。
  • 其他的点双连通分量大小至少为\(2\)个点。

与强联通分量类似,用一个栈来维护,如果这个点第一次被访问时,把该节点进栈。当割点判定法则中的条件 \(dfn_x\leq low_y\)时,无论\(x\)是否为根,都要从栈顶不断弹出节点,直至节点\(y\)被弹出,刚才弹出的所有节点与节点\(x\)一起构成一个点双连通分量。

我的理解:

一个割点必然包含在多个点双连通分量中,而一个非割点却至多包含在一个点双连通分量中。所以,当割点判定法则得到满足时,当前点即割点和它在搜索树上的若干子节点必然构成了一个点双连通分量,这与强连通分量的求解是类似的。而与强连通分量不同的是,由于一个割点可能包含在多个点双连通分量中,所以我们需要将出栈操作放在遍历子节点的循环中,并每次额外将割点\(x\)也加入点双连通分量中。

\(Code:\)

inline void Tarjan(int x,int root)
{
dfn[x]=low[x]=++cnt;
Stack.push(x);
int flag=0;
if(x==root&&!Last[x])
{
Stack.pop();T++;
cut[x]=true;
con[T].push_back(x);
return;
}
for(int i=Last[x];i;i=e[i].next)
{
int y=e[i].ver;
if(!dfn[y])
{
Tarjan(y,root);
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x])
{
int top=0;T++;
while(top!=y)
{
top=Stack.top();
Stack.pop();
con[T].push_back(top);
}
con[T].push_back(x);
flag++;
if(x!=root||flag>1)cut[x]=true;
}
}
else low[x]=min(low[x],dfn[y]);
}
}

缩点

和强连通分量一样,我们也可以利用双连通分量缩点。而双连通分量的缩点的性质是:将无向图缩为一棵树。具体地,使用点的双连通分量还是边的双连通分量等问题,我们需要具体分析并讨论。

逃不掉的路

Description

现代社会,路是必不可少的。任意两个城镇都有路相连,而且往往不止一条。按理说条条大路通罗马,大不了绕行其他路呗。可小鲁却发现:从a城到b城不管怎么走,总有一些逃不掉的必经之路

他想请你计算一下,a到b的所有路径中,有几条路是逃不掉的?

Input Format

第一行是n和m,用空格隔开。

接下来m行,每行两个整数x和y,用空格隔开,表示x城和y城之间有一条长为1的双向路。

第m+2行是q。接下来q行,每行两个整数a和b,用空格隔开,表示一次询问。

Output Format

对于每次询问,输出一个正整数,表示a城到b城必须经过几条路。

Sample Input

5 5
1 2
1 3
2 4
3 4
4 5
2
1 4
2 5

Sample Output

0
1

解析

我们可以先放宽题目限制,假设给出的图是一棵树,那么如何求解呢?

对于树,我们显然使用树上点距的方法来求解。\(dis(x,y)=depth_x+depth_y-2depth_{lca(x,y)}\)。\(lca\)用树上倍增求解就可以了,这些都是模板,就不过多讨论了。

那么这是无向图呢,\(e-DCC\)缩点构树就可以了。

为什么不用\(v-DCC\)?\(v-DCC\)比较特殊,由于割点同时被多个点双连通分量包含,所以缩点需要保留割点,会增加点的数量,一般模型中,我们使用\(e-DCC\)缩点。

\(Code:\)

#include<bits/stdc++.h>
using namespace std;
const int N=1e5*2,M=2e5*4,Q=1e5*2,MaxlogN=25;
int n,m,q,Last[M*2],Last_[M*2],dfn[N],low[N],cnt,tot,t=1,t_,T,begin[Q],end[Q],bridge[M*2],con[N],depth[N],f[N][MaxlogN];
struct EDGE{int ver,next;}e[M*2],e_[M*2];
struct LINK{int x,y;}Link[M*2];
inline void insert(int x,int y)
{
e[++t].ver=y;e[t].next=Last[x];Last[x]=t;
}
inline void insert_(int x,int y)
{
e_[++t_].ver=y;e_[t_].next=Last_[x];Last_[x]=t_;
}
inline void input(void)
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int x,y;
scanf("%d%d",&x,&y);
insert(x,y);
insert(y,x);
}
scanf("%d",&q);
for(int i=1;i<=q;i++)
scanf("%d%d",&begin[i],&end[i]);
}
inline void Tarjan(int x,int inedge)
{
dfn[x]=low[x]=++cnt;
for(int i=Last[x];i;i=e[i].next)
{
int y=e[i].ver;
if(!dfn[y])
{
Tarjan(y,i);
low[x]=min(low[x],low[y]);
if(low[y]>dfn[x])
{
bridge[i]=bridge[i^1]=true;
Link[++T]=(LINK){x,y};
}
}
else if(i!=(inedge^1))low[x]=min(low[x],dfn[y]);
}
}
inline void dfs(int x)
{
con[x]=tot;
for(int i=Last[x];i;i=e[i].next)
{
if(bridge[i])continue;
int y=e[i].ver;
if(!con[y])dfs(y);
}
}
inline void colored(void)
{
for(int i=1;i<=n;i++)
if(!con[i])tot++,dfs(i);
}
inline void build(void)
{
for(int i=1;i<=T;i++)
{
insert_(con[Link[i].x],con[Link[i].y]);
insert_(con[Link[i].y],con[Link[i].x]);
}
}
inline void init(int x,int dep)
{
depth[x]=dep;
for(int i=Last_[x];i;i=e_[i].next)
{
int y=e_[i].ver;
if(f[x][0]==y)continue;
f[y][0]=x;
init(y,dep+1);
}
}
inline void dp(void)
{
f[1][0]=-1;
for(int k=1;(1<<k)<tot;k++)
{
for(int i=1;i<=tot;i++)
{
if(f[i][k-1]<0)f[i][k]=-1;
else f[i][k]=f[f[i][k-1]][k-1];
}
}
}
inline int LCA(int x,int y)
{
if(depth[x]>depth[y])
x^=y^=x^=y;
for(int d=depth[y]-depth[x],i=0;d;d>>=1,i++)
if(1&d)y=f[y][i];
if(x==y)return x;
for(int i=MaxlogN-2;i>=0;i--)
{
if(f[x][i]^f[y][i])
{
x=f[x][i];
y=f[y][i];
}
}
return f[x][0];
}
inline void solve(void)
{
for(int i=1;i<=q;i++)
{
int x=con[begin[i]],y=con[end[i]];
printf("%d\n",depth[x]+depth[y]-2*depth[LCA(x,y)]);
}
}
int main(void)
{
input();
Tarjan(1,0);
colored();
build();
init(1,0);
dp();
solve();
return 0;
}

矿场搭建(BZOJ2730)

Description

煤矿工地可以看成是由隧道连接挖煤点组成的无向图。

为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。

请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。

Input Format

输入文件有若干组数据,每组数据的第一行是一个正整数 N(N≤500),表示工地的隧道数,接下来的 N 行每行是用空格隔开的两个整数 S 和 T,表示挖煤点 S 与挖煤点 T 由隧道直接连接。输入数据以 0 结尾。

Output Format

输入文件中有多少组数据,输出文件 output.txt 中就有多少行。

每行对应一组输入数据的 结果。其中第 i 行以 Case i: 开始,其后是用空格隔开的两个正整数,第一个正整数表示对于第 i 组输入数据至少需 要设置几个救援出口,第二个正整数表示对于第 i 组输入数据不同最少救援出口的设置方案总 数。

输入数据保证答案小于 2^64。输出格式参照以下输入输出样例。

Sample Input

 9
1 3
4 1
3 5
1 2
2 6
1 5
6 3
1 6
3 2
6
1 2
1 3
2 4
2 5
3 6
3 7
0

Sample Output

Case 1: 2 4
Case 2: 4 1

解析

对于每一个发生事故的点,可能会将原图分为几个联通块,使得我们需要在不同的联通块设置更多的救援出口,这就和我们割点模型有些相似了。

进一步地,我们需要求出图中的每一个割点和各个点双连通分量,对于每一个点双连通分量,如果当中包含了一个割点,则说明这当中我们需要在非割点位置设置一个救援出口(如果割点位置发生事故,该连通块会被独立,必须额外独立设置一个救援出口),出口数累加,方案数按照点双连通分量大小减去一(一个割点不能放)累乘即可。如果当中包含了两个割点,则说明无论什么情况这一块都不会被"独立",不需要设置救援出口。

最后,还有一种特殊情况需要判断:若整张图就是一个点双连通分量,即没有割点,那么我们至少设置两个出口(若设置一个,可能恰好在出口处发生事故),方案数为\(\frac{n(n-1)}{2}\)。

算法流程:

先\(tarjan\)求一下所有的点双连通分量。然后对于每一个点双连通分量,分类讨论:

  • 只有一个割点,必须选一个非割点。
  • 有多于\(1\)个割点,不用选
  • 没有个割点,必须选两个。

\(Code:\)

#include<bits/stdc++.h>
using namespace std;
const int M=500*3,N=1000;
int m,n,Last[N],t,dfn[N],low[N],cnt,T,cut[N],total[N],vis[N];
long long ans1,ans2=1;
struct edge{int ver,next;}e[M];
vector < int >con[N];
stack < int > Stack;
inline void insert(int x,int y)
{
e[++t].ver=y;e[t].next=Last[x];Last[x]=t;
}
inline bool input(void)
{
scanf("%d",&n);
if(n==0)return false;
for(int i=1;i<=n;i++)
{
int x,y;
scanf("%d%d",&x,&y);
insert(x,y);
insert(y,x);
if(!vis[x])vis[x]=true,m++;
if(!vis[y])vis[y]=true,m++;
}
return true;
}
inline void Tarjan(int x,int root)
{
dfn[x]=low[x]=++cnt;
Stack.push(x);
int flag=0;
if(x==root&&!Last[x])
{
Stack.pop();T++;
cut[x]=true;
con[T].push_back(x);
return;
}
for(int i=Last[x];i;i=e[i].next)
{
int y=e[i].ver;
if(!dfn[y])
{
Tarjan(y,root);
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x])
{
int top=0;T++;
while(top!=y)
{
top=Stack.top();
Stack.pop();
con[T].push_back(top);
}
con[T].push_back(x);
flag++;
if(x!=root||flag>1)cut[x]=true;
}
}
else low[x]=min(low[x],dfn[y]);
}
}
inline void solve(void)
{
for(int i=1;i<=T;i++)
{
if(con[i].size()==m)
{
ans1=2;ans2=m*(m-1)/2;
return;
}
for(int j=0;j<con[i].size();j++)
if(cut[con[i][j]])total[i]++;
if(total[i]==1)
ans1+=1LL,ans2*=1LL*(con[i].size()-total[i]);
}
}
inline void Reset(void)
{
for(int i=1;i<=T;i++)
con[i].clear();
t=0;ans1=0;ans2=1;T=0;cnt=0;m=0;
memset(Last,0,sizeof Last);
memset(total,0,sizeof total);
memset(cut,0,sizeof cut);
memset(dfn,0,sizeof dfn);
memset(low,0,sizeof low);
memset(e,0,sizeof e);
memset(vis,0,sizeof vis);
while(!Stack.empty())Stack.pop();
}
int main(void)
{
int index=0;
while(input())
{
index++;
Tarjan(1,1);
solve();
printf("Case %d: %lld %lld\n",index,ans1,ans2);
Reset();
}
return 0;
}

<后记>

『Tarjan算法 无向图的双联通分量』的更多相关文章

  1. 『Tarjan算法 无向图的割点与割边』

    无向图的割点与割边 定义:给定无相连通图\(G=(V,E)\) 若对于\(x \in V\),从图中删去节点\(x\)以及所有与\(x\)关联的边后,\(G\)分裂为两个或以上不连通的子图,则称\(x ...

  2. 无向图边双联通分量 tarjan 模板

    #include <bits/stdc++.h> using namespace std; const int MAXN = 100005; const int MAXM = 500005 ...

  3. 『Pushbox 点双联通分量』

    Pushbox Description 周婧涵和她的小伙伴们发明了一个新游戏.游戏名字很准确,但不是特别有 创意.她们称之为"推动箱子在谷仓周围找到正确的位置,不要移动干草"游戏 ...

  4. 『Tarjan算法 有向图的强连通分量』

    有向图的强连通分量 定义:在有向图\(G\)中,如果两个顶点\(v_i,v_j\)间\((v_i>v_j)\)有一条从\(v_i\)到\(v_j\)的有向路径,同时还有一条从\(v_j\)到\( ...

  5. Tarjan 强连通分量 及 双联通分量(求割点,割边)

    Tarjan 强连通分量 及 双联通分量(求割点,割边) 众所周知,Tarjan的三大算法分别为 (1)         有向图的强联通分量 (2)         无向图的双联通分量(求割点,桥) ...

  6. Spoj 2878 KNIGHTS - Knights of the Round Table | 双联通分量 二分图判定

    题目链接 考虑建立原图的补图,即如果两个骑士不互相憎恨,就在他们之间连一条无向边. 显而易见的是,如果若干个骑士在同一个点数为奇数的环上时,他们就可以在一起开会.换句话说,如果一个骑士被一个奇环包含, ...

  7. 图连通性【tarjan点双连通分量、边双联通分量】【无向图】

    根据 李煜东大牛:图连通性若干拓展问题探讨 ppt学习. 有割点不一定有割边,有割边不一定有割点. 理解low[u]的定义很重要. 1.无向图求割点.点双联通分量: 如果对一条边(x,y),如果low ...

  8. 大白书中无向图的点双联通分量(BCC)模板的分析与理解

    对于一个无向图,如果任意两点至少存在两条点不重复(除起点和终点外无公共点)的路径,则这个图就是点双联通. 这个要求等价于任意两条边都存在于一个简单环(即同一个点不能在圈中出现两次)中,即内部无割点. ...

  9. [J]computer network tarjan边双联通分量+树的直径

    https://odzkskevi.qnssl.com/b660f16d70db1969261cd8b11235ec99?v=1537580031 [2012-2013 ACM Central Reg ...

随机推荐

  1. 在命令行输入python出现“Warning:This Python interpreter is in a conda environment, but the environment has not been activated. Libraries may fail to load. To activate this environment please see https://conda.

    [现象] 在命令行输入python出现“Warning:This Python interpreter is in a conda environment, but the environment h ...

  2. python常见的函数和类方法

    在学python编程时 常常会遇到些常见的函数 记录学习 1. getattr函数 """ getattr() 函数用于返回一个对象属性值. 语法: getattr(ob ...

  3. maya cmds pymel selectType() 选择类型切换

    maya cmds pymel selectType() 选择类型切换 import maya.cmds as cmds cmds.selectType( polymeshFace = True ) ...

  4. Ducci 队列 -基础queue,set

    https://vjudge.net/contest/185301#problem/B 用队列记录,set的不重复性来判断 //#include<bits/stdc++.h> #inclu ...

  5. redis的雪崩与穿透原理的浅理解

    首先列一下主要说什么, 1.什么是Redis缓存的雪崩? 2.什么是Redis缓存的穿透? 3.Redis缓存崩溃会怎么样? 4.怎么预防Redis缓存崩溃? 1.什么是Redis缓存的雪崩? 举个栗 ...

  6. yum安装k8s集群(kubernetes)

    此案例是以一个主,三个node来部署的,当然node可以根据自己情况部署 192.168.1.130 master 192.168.1.131 node1 192.168.1.132 node2 19 ...

  7. 本机是wifi,虚拟机无法连接外网问题

    1.首先看自己本机的各网口是否都启动. 2.在虚拟机中的虚拟网络编辑器中,选择桥接模式,并选择对应第一步的WLAN端口. 3.在虚拟机设置中选择自定义,选择第二部中选的VMnet2即可上网了.

  8. Android Studio 重写方法时参数命名异常

    Android Studio 重写方法时参数命名异常 Android Studio 重写方法时参数名称乱掉可以通过下载相应源码解决

  9. 查看Android应用包名、Activity的几个方法

    一.有源码情况 直接打开AndroidManifest.xml文件,找到包含android.intent.action.MAIN和android.intent.category.LAUNCHER对应的 ...

  10. 把ssl模块加入到已经编译好的apache中实现HTTPS

    为了使Apache支持https访问,系统需要安有apache.openssl.mod_ssl.so 1.安装openssl: 基本上系统都已经安装了,在/usr/bin/openssl下,直接使用o ...