无向图的双连通分量

边的双连通分量(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,点双连通分量,现在就发现了一个作用:就是统计一下将割点断开后,连通块的数量。

posted @ 2021-11-21 11:58  艾特玖  阅读(85)  评论(0)    收藏  举报