<更新提示>

<第一次更新>


<正文>

无向图的双连通分量

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

无向图图的极大点双连通子图被称为"点双连通分量",记为"\(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. IMCASH:2019年区块链不会风平浪静,至少还有10件事值得期待

    当我们在说2019年是值得期待的一年时,我们还是得做到有根有据.那么,2019年在区块链行业都会发生哪些引导行业风向.影响整个行业的事件呢? 今天,白话区块链带着大家顺着时间线梳理一下. 01 第一季 ...

  2. 如何快速求解第一类斯特林数--nlog^2n + nlogn

    目录 参考资料 前言 暴力 nlog^2n的做法 nlogn的做法 代码 参考资料 百度百科 斯特林数 学习笔记-by zhouzhendong 前言 首先是因为这道题,才去研究了这个玩意:[2019 ...

  3. 2019-3-22c# TextBox只允许输入数字,禁用右键粘贴,允许Ctrl+v粘贴数字

    TextBox 禁止复制粘贴 ShortcutsEnabled =false TextBox只允许输入数字,最大长度为10 //TextBox.ShortcutsEnabled为false 禁止右键和 ...

  4. C++输出

    setiosflags 意思就是设置输入输出的标志iso::fixed 是操作符setiosflags 的参数之一,该参数指定的动作是以带小数点的形式表示浮点数,并且在允许的精度范围内尽可能的把数字移 ...

  5. 阿森纳vs托特纳姆热刺

    阿森纳vs托特纳姆热刺之间进行的英格兰足球联盟杯比赛时间为2018年12月20日 03:45.本场比赛的亚盘初盘为阿森纳0.0,大小球初盘3.0,角球盘口10.5.在此之前,阿森纳和托特纳姆热刺两队进 ...

  6. BZOJ5326 : [Jsoi2017]博弈

    将所有物品按照$b$的选择顺序排序,则先手在任意前$i$个物品中最多只能拿走$\lceil\frac{i}{2}\rceil$个物品. 将每个物品的价值设为$a+b$,那么答案为先手拿走的价值和减去所 ...

  7. android 第一次作业

    天气预报界面截图: 源码coding地址:https://coding.net/u/dsy1600802076/p/android/git/tree/master

  8. 使用secureCRT和Telnet将文件压缩导出到Ubuntu中,到Ubuntu中加压缩发现:tar解压包的时候出现错误gzip: stdin: not in gzip format tar: Child returned status 1 tar: Error is not recoverable: exiting now

    细节描述: 问题如题所示:查找博客园和CSDN上查找问题,得到问题解决方法大致如下: 1 修改解压缩命令: 由 tar zxvf software_package.tar.gz变为tar xvf so ...

  9. 小程序wx:for循环列表数量的限制

    数据有100条,我们只要页面显示一部分,就要通过index来限制.index<3,就是显示序列0,1,2这三条数据.具体写法: <block wx:for='{{list}}' wx:ke ...

  10. Meltdown Attack

    1. 引言 2018年1月3日,Google Project Zero(GPZ)团队安全研究员Jann Horn在其团队博客中爆出CPU芯片的两组漏洞,分别是Meltdown与Spectre. Mel ...