无向图的双连通分量
边的双连通分量(e-DCC)
桥:将桥断开后,连通图,会变成两个连通块。
e-DCC的概念:极大的不包括桥的连通块。
关于e-DCC的性质:其中任意两点之间,都有两条不相交的路径。
注意:其中的不相交,指的是,没有公共路径。
缩点后的图是一颗树。
因此引出的一个常见问题:
给一个无向图最少加几条边后,能够使其任意两点都有两条无公共路径的路径。
$解决:缩点后统计度数为1的点的个数cnt,答案为 \lfloor \frac {cnt}2 \rfloor $
其代码,几乎与求SCC相同。
接下来,看代码,因为代码与SCC几乎相同,不做过多注释。
const int N = 5e3 + 10,M = 2e4 + 10;
int n,m;
int h[N],e[M],ne[M],idx;
int dfn[N],low[N],timestamp;
int id[N],dcc_cnt;
stack<int> s;
bool is_bridge[M];//标记边是否为桥
//双连通分量,还有一大特点就是,没有了标记数组
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void tarjan(int u,int fa)
{
dfn[u]=low[u]=++timestamp;
s.push(u);
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
if(j==fa) continue;//如果指向父节点,则直接忽略
if(!dfn[j])
{
tarjan(j,u);
low[u]=min(low[u],low[j]);
if(dfn[u]<low[j]) is_bridge[i]=is_bridge[i^1]=1;//如果在j再也无法到u上方,则u-j是一个桥
}
else low[u]=min(low[u],dfn[j]);//同时,没有标记数组后,我们每次都需要对low[u]进行更新。
}
if(dfn[u]==low[u])
{
int y;
dcc_cnt++;
do
{
y=s.top();
s.pop();
id[y]=dcc_cnt;
}while(y!=u);
}
}
//缩点
for(int i=1;i<=n;i++)
for(int j=h[i];~j;j=ne[j])
{
int k = e[j];
if(id[i]!=id[k]) add(id[i],id[k]);
}
关于,边的双连通分量,我们可以引出简单的几个应用
- 每有一个桥,我们就可以断开它,获得多一个连通块。
- 桥内部,每两个点之间有两条不相交的路径。
点的双连通分量(v-DCC)
割点:将割点去掉后,连通图将分为,不同的连通块。
注意:每个割点至少属于两个连通分量,这就意味着,缩点后,同时会缩到两个点内
v-DCC:极大的不包含割点的连通块
关于这个问题,就要麻烦的多了,我们拆成两个部分。
1.如何求割点
O x low(y)>=dfn(x)时分情况讨论
/ 1.如果不是根节点,那就是
O y 2.如果是根节点,当连接在其上的V-DCC数量大于1时,则是一个割点。
2.如何求双连通分量
if(dfn[x]<=low[y])
{
cnt++;//连接在该割点上的V-DCC数量
if(x非根节点||cnt>1) x是割点
将栈中元素弹出直至弹出y为止
且x也属于该"点双连通分量"
}
而关于,点的双连通分量,我们先引入代码,在对其进行分析作用
const int N = 1010,M = 510;
int h[N],e[M],ne[M],idx;
int dfn[N],low[N],timestamp;//经典时间戳
int dcc_cnt;
bool cut[N];//统计点是否为割点
stack<int> s;
vector<int> dcc[N];//将每个v-DCC的点存起来
int root,n,m;//每一次tarjan开始便利时的根节点
void add(int a,int b)
{
e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}
void tarjan(int u)
{
dfn[u]=low[u]=++timestamp;
s.push(u);
if(u==root&&h[u]==-1)//孤立点也是一个v-DCC
{
dcc_cnt++;
dcc[dcc_cnt].push_back(u);
return ;
}
int cnt = 0;//用来统计每个割点断开后,会出现的v-DCC或者说连通块个数
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
if(!dfn[j])
{
tarjan(j);
low[u]=min(low[u],low[j]);
if(dfn[u]<=low[j])//判断u是否为割点
{
cnt++;//则出现一个连通块
if(u!=root||cnt>1) cut[u]=1;//如果u不是根节点,或者是根节点,但是连接在其上的v-DCC大于1,则u也为一个割点
++dcc_cnt;
int y;
do
{
y = s.top();
s.pop();
dcc[dcc_cnt].push_back(y);//将v-DCC中所有的点存起来
}while(y!=j);
dcc[dcc_cnt].push_back(u);//将割点放入,但是不将割点从栈中弹出。
}
}else low[u]=min(low[u],dfn[j]);
}
}
写题太少了,emm,点双连通分量,现在就发现了一个作用:就是统计一下将割点断开后,连通块的数量。

浙公网安备 33010602011771号