点双联通分量(V-DCC)的求法 & 缩点

用一个stack来维护,求割点的同时求点双联通分量就可以,

这里要区别求边双联通分量时只需要在原图中去掉所有割边

即可。另外因为一个割点可以同时属于多个不同的点双联通

分量(普通点只属于一个),所以这里采用记录联通块中所

包含的点的方法来记录每个点所属于的联通块们。

初始化:

        tot=tot2=1;
        memset(head,0,sizeof(head));
        memset(Next,0,sizeof(Next));
        memset(ver,0,sizeof(ver));
        memset(cut,0,sizeof(cut));
        memset(hc,0,sizeof(hc));
        memset(vc,0,sizeof(vc));
        memset(nc,0,sizeof(nc));
        memset(new_id,0,sizeof(new_id));
        cnt=top=num=0;
        memset(low,0,sizeof(low));
        memset(dfn,0,sizeof(dfn));
        memset(stackk,0,sizeof(stackk));
        for(int i=0;i<=n;++i)dcc[i].clear();

代码代码:

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include<algorithm>
#include <set>
#include <queue>
#include <stack>
#include<vector>
#include<map>
#include<ctime>
#define ll long long
using namespace std;
const int N=10010;
const int M=10010*4;
int head[N];
int ver[M];
int Next[M];
int tot,n,m;
void add(int x,int y)
{
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
}
int root;
vector<int>dcc[N];
int stackk[N];
int dfn[N],low[N];
int num=0;//时间戳
int top;//stackk
int cnt=0;//联通块数目
bool cut[N];//割点判断
void tarjan(int x)
{
    dfn[x]=low[x]=++num;
    stackk[++top]=x;
    if(x==root&&head[x]==0)
    {
        dcc[++cnt].push_back(x);//cnt联通块标号
        return ;
    }
    int flag=0;
    for(int i=head[x];i;i=Next[i])
    {
        int y=ver[i];
        if(!dfn[y])
        {
            tarjan(y);
            low[x]=min(low[x],low[y]);
            if(low[y]>=dfn[x])
            {
                flag++;
                if(x!=root||flag>1)cut[x]=true;
                cnt++;
                int z;
                do//弹出的元素与x一起构成一个联通块(或者说割点的子树中的节点+割点?)
                {
                    z=stackk[top--];
                    dcc[cnt].push_back(z);
                }while(z!=y);
                dcc[cnt].push_back(x);
            }
        }
        else low[x]=min(low[x],dfn[y]);
    }
}
int main()
{
    while(cin>>n>>m)
    {
        tot=1;
        for(int i=1;i<=m;++i)
        {
            int x,y;
            cin>>x>>y;
            if(x==y)continue;
            add(x,y);
            add(y,x);
        }
        for(int i=1;i<=n;++i)
        {
            if(!dfn[i])root=i,tarjan(i);
        }
        /*for(int i=1;i<=n;++i)
            if(cut[i])printf("%d ",i);*/
        //上面求割点同时求V-DCC
        //下面输出每个联通块中的点
        for(int i=1;i<=cnt;++i)
        {
            for(int j=0;j<dcc[i].size();++j)cout<<i<<" "<<dcc[i][j]<<endl;
        }

    }
    return 0;
}

加上缩点(注意^的使用,其次量较多,各量之间不要弄混):

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include<algorithm>
#include <set>
#include <queue>
#include <stack>
#include<vector>
#include<map>
#include<ctime>
#define ll long long
using namespace std;
const int N=10010;
const int M=10010*4;
int head[N];
int ver[M];
int Next[M];
int tot,n,m;
void add(int x,int y)
{
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
}
int root;
vector<int>dcc[N];
int stackk[N];
int dfn[N],low[N];
int num=0;//时间戳
int top;//stackk
int cnt=0;//联通块数目
bool cut[N];//割点判断
void tarjan(int x)
{
    dfn[x]=low[x]=++num;
    stackk[++top]=x;
    if(x==root&&head[x]==0)
    {
        dcc[++cnt].push_back(x);//cnt联通块标号
        return ;
    }
    int flag=0;
    for(int i=head[x];i;i=Next[i])
    {
        int y=ver[i];
        if(!dfn[y])
        {
            tarjan(y);
            low[x]=min(low[x],low[y]);
            if(low[y]>=dfn[x])
            {
                flag++;
                if(x!=root||flag>1)cut[x]=true;
                cnt++;
                int z;
                do//弹出的元素与x一起构成一个联通块(或者说割点的子树中的节点+割点?)
                {
                    z=stackk[top--];
                    dcc[cnt].push_back(z);
                }while(z!=y);
                dcc[cnt].push_back(x);
            }
        }
        else low[x]=min(low[x],dfn[y]);
    }
}
int tot2=1;
int new_id[N];

int hc[N];
int vc[M];
int nc[M];
void add_c(int x,int y)
{
    vc[++tot2]=y;
    nc[tot2]=hc[x];
    hc[x]=tot2;
}
int main()
{
    while(cin>>n>>m)
    {
        tot=1;//方便用^运算访问各边的终点
        for(int i=1;i<=m;++i)
        {
            int x,y;
            cin>>x>>y;
            if(x==y)continue;
            add(x,y);
            add(y,x);
        }
        for(int i=1;i<=n;++i)
        {
            if(!dfn[i])root=i,tarjan(i);
        }
        /*for(int i=1;i<=n;++i)
            if(cut[i])printf("%d ",i);*/
        //上面求割点同时求V-DCC
        //下面输出每个联通块中的点
        for(int i=1;i<=cnt;++i)
        {
            for(int j=0;j<dcc[i].size();++j)cout<<i<<" "<<dcc[i][j]<<endl;
        }



        //缩点
        tot2=1;
        int num2=cnt;
        for(int i=1;i<=n;++i)
        {
            if(cut[i])new_id[i]=++num2;//缩点后割点的新编号,相当于每个割点单独作为一个联通块
        }
        for(int i=1;i<=cnt;++i)
        {
            for(int j=0;j<dcc[i].size();++j)
            {
                int x=dcc[i][j];
                if(cut[x])//一个联通块中有且只有一个割点,通过割点们把这些联通块连接起来;
                {
                    add_c(i,new_id[x]);
                    add_c(new_id[x],i);
                }
                else new_id[x]=i;//其余点均只属于一个联通块
            }
        }

        //输出缩点后的图中各点之间的邻接关系,再次注意^符号的使用,i从2开始,每次加2,<tot2而非<=;
        for(int i=2;i<tot2;i+=2)
            cout<<vc[i^1]<<"   "<<vc[i]<<endl;



    }
    return 0;
}

The end;

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值