菜鸟系列——双连通分量

菜鸟就要老老实实重新学起:

双连通分量

就是无向图中的点集满足:任意两点之间能够有不止一条通路。

边双连通分量

就是两点间不含有桥的点集,桥就是满足去除该边之后图不再连通的边,也就是任意两点间有不同的边集可以到达,边连通分量之间可能有桥连接。

求解就是直接用求有向图强连通分量的tarjan算法,因为是无向图,深搜时上不能返回父节点就行了。

模版:

#define N 110

vector<int>g[N];
stack<int>st;
// 深度优先搜索访问次序, 能追溯到的最早的次序
int dfn[N],low[N];
// 检查是否在栈中, 记录每个点在第几号强连通分量里
int inStack[N],belong[N];
// 索引号,双连通分量个数
int index,cnt;
int n,m;

void init()
{
    for(int i=0;i<N;i++)
        g[i].clear();
    while(!st.empty())st.pop();
	memset(dfn, 0, sizeof(dfn));
	memset(low, 0, sizeof(low));
	memset(inStack, 0, sizeof(inStack));
	index = cnt = 1;
}

void tarjan(int x,int fa)
{
	int i;
	// 刚搜到一个节点时low = dfn
	low[x] = dfn[x] = index;
	index++;
	st.push(x);
	inStack[x] = 1;
	int len = g[x].size();
	int mark = 0;
	for(i=0;i<len;i++)
	{
	    int t=g[x][i];
	    //无向图双连通分量,mark防重边。
	    if(!mark && t==fa)
        {
            mark=1;
            continue;
        }
		if(!dfn[t])
		{
			tarjan(t,x);
			// 回溯的时候改变当前节点的low值
			low[x] = min(low[x], low[t]);
		}
		// 如果新搜索到的节点已经被搜索过而且现在在栈中
		else if(inStack[t])
		{
		    // 更新当前节点的low值,这里的意思是两个节点之间有一条可达边,
		    // 而前面节点已经在栈中,那么后面的节点就可能和前面的节点在一个联通分量中
			low[x] = min(low[x], dfn[t]);
		}
	}

	// 最终退回来的时候 low == dfn , 没有节点能将根节点更新,那必然就是根节点
	if(low[x] == dfn[x])
	{
		int temp;
		// 一直出栈到此节点, 这些元素是一个双联通分量
		while(!st.empty())
		{
			temp = st.top();
			st.pop();
			belong[temp] = cnt; // 标记双联通分量
		 	inStack[temp] = 0;
		 	if(temp == x)
		 		break;
		}
		cnt++;
	}
}

int solve()
{
    for(int i = 1; i <= n; i++)
        if(!dfn[i])
            tarjan(i,i);
    return cnt;
}


点双连通分量

就是内部不含有割点的点集,割点就是满足去除该点之后图不联通点,也就是任意两点间有不同的点集可以到达,两个点联通分量之间可能有割点连接,割点属于两个联通分量。

求解也是通过tarjan深搜,但栈中存放边,将图分成不同的点联通分量也就是块。


模版:

#define N 112345

struct node
{
    int x,y;
    node(int a=0, int b=0)
    {
        x=a;y=b;
    }
}tn;
//blocks[]存每个块包含的点,bridge存是桥的边
vector<int>g[N],blocks[N];
vector<node>bridge;
stack<node>st;
// 深度优先搜索访问次序, 能追溯到的最早的次序
int dfn[N],low[N];
bool vis[N];
// 索引号,块的个数
int index,cnt;
int n,m;

void init()
{
    for(int i=0;i<N;i++)
        g[i].clear(),blocks[i].clear();
    bridge.clear();
    while(!st.empty())st.pop();
	memset(dfn, 0, sizeof(dfn));
	memset(low, 0, sizeof(low));
	index = cnt = 1;
}

void judge(int u,int v)
{
    int x,y;
    node temp;
    memset(vis,false,sizeof(vis));
    while(!st.empty())
    {
        temp = st.top();st.pop();
        x=temp.x;y=temp.y;
        if(!vis[y])blocks[cnt].push_back(y),vis[y]=true;
        //一直到最后一条树枝边为止,起点并不属于这个双连通分量,如果属于,已在后向边中加入。
        if(x==u) break;
        if(!vis[x])blocks[cnt].push_back(x),vis[x]=true;
    }
    cnt++;
}

void tarjan(int x,int fa)
{
	low[x] = dfn[x] = index++;
	int len = g[x].size();
	for(int i=0;i<len;i++)
	{
	    int t=g[x][i];
	    if(t==fa)
            continue;
		if(!dfn[t] && dfn[t]<dfn[x])
		{
		    //加入树枝边
		    st.push(node(x,t));
			tarjan(t,x);
			low[x] = min(low[x], low[t]);
			if(dfn[x]<=low[t])
                judge(x,t);
            if(dfn[x]<low[t])
                bridge.push_back(node(x,t));
		}
		else if(dfn[t] < dfn[x])
        {
            //加入后向边
            st.push(node(x,t));
			low[x] = min(low[x], dfn[t]);
        }
	}
}

int solve()
{
    for(int i = 1; i <= n; i++)
        if(!dfn[i])
            tarjan(i,i);
    return cnt;
}

eg:

POJ3352 Road Construction

http://poj.org/problem?id=3352

题意:
给出各景点之间的路线,要求在修任意一条路时还能够到达所有景点,求要至少多修多少临时的桥。
思路:

就是求加入最少的边使得整个图变成双连通图,只要求出所有双连通分量然后缩点连线,使之成为双连通图就行了,

求双连通分量缩点用tarjan算法,之后对于所有的度数为一的点连线即可。答案就是(度数唯一的点+1)/2;

code:
#define N 112345

int n,m;
int flag,sum,ave,ans,res,len,ans1,ans2;
int a[N],b[N];
vector<int>g[N];
stack<int>st;
int dfn[N],low[N];
int inStack[N],belong[N];
int index,cnt;

void init()
{
    for(int i=0;i<N;i++)
        g[i].clear();
    while(!st.empty())st.pop();
	memset(dfn, 0, sizeof(dfn));
	memset(low, 0, sizeof(low));
	memset(inStack, 0, sizeof(inStack));
	memset(a,0,sizeof(a));
	index = cnt = 1;
}

void tarjan(int x, int fa)
{
	int i, a;
	low[x] = dfn[x] = index;
	index++;
	st.push(x);
	inStack[x] = 1;
	int len = g[x].size();
	for(i=0;i<len;i++)
	{
	    int t=g[x][i];
	    if(t == fa)
            continue;
		if(!dfn[t])
		{
			tarjan(t,x);
			low[x] = min(low[x], low[t]);
		}
		else if(inStack[t])
			low[x] = min(low[x], dfn[t]);
	}

	if(low[x] == dfn[x])
	{
		int temp;
		while(!st.empty())
		{
			temp = st.top();
			st.pop();
			belong[temp] = cnt; 
		 	inStack[temp] = 0;
		 	if(temp == x)
		 		break;
		}
		cnt++;
	}
}

int solve()
{
    for(int i = 1; i <= n; i++)
        if(!dfn[i])
            tarjan(i,i);
    return cnt;
}
int main()
{
    int i,j,k,kk,t,x,y,z;
    while(scanf("%d%d",&n,&m)!=EOF&&n)
    {
        init();
        for(i=0;i<m;i++)
        {
            scanf("%d%d",&x,&y);
            g[x].push_back(y);
            g[y].push_back(x);
        }
        solve();
        for(i=1;i<=n;i++)
            for(j=0;j<g[i].size();j++)
                if(belong[i]!=belong[g[i][j]])
                    a[belong[i]]++,a[belong[g[i][j]]]++;
        sum=0;
        for(i=1;i<cnt;i++)
            if(a[i]==2)
                sum++;
        sum++;
        printf("%d\n",sum/2);
    }
    return 0;
}



POJ3177 Redundant Pathsac

http://poj.org/problem?id=3177

题意:
n个农场,要求彼此之间至少有两条路线,求要新建多少路。
思路:
同上题完全一样,就是这题会有重边,注意加flag标记下。
code:

#define N 112345

int n,m;
int flag,sum,ave,ans,res,len,ans1,ans2;
int a[N],b[N];
vector<int>g[N];
stack<int>st;
int dfn[N],low[N];
int inStack[N],belong[N];
int index,cnt;

void init()
{
    for(int i=0;i<N;i++)
        g[i].clear();
    while(!st.empty())st.pop();
	memset(dfn, 0, sizeof(dfn));
	memset(low, 0, sizeof(low));
	memset(inStack, 0, sizeof(inStack));
	memset(a,0,sizeof(a));
	index = cnt = 1;
}

void tarjan(int x, int fa)
{
	int i, a;
	low[x] = dfn[x] = index;
	index++;
	st.push(x);
	inStack[x] = 1;
	int len = g[x].size();
	flag=0;
	for(i=0;i<len;i++)
	{
	    int t=g[x][i];
	    if(t == fa && !flag)
        {
            flag=1;
            continue;
        }
		if(!dfn[t])
		{
			tarjan(t,x);
			low[x] = min(low[x], low[t]);
		}
		else if(inStack[t])
			low[x] = min(low[x], dfn[t]);
	}

	if(low[x] == dfn[x])
	{
		int temp;
		while(!st.empty())
		{
			temp = st.top();
			st.pop();
			belong[temp] = cnt; 
		 	inStack[temp] = 0;
		 	if(temp == x)
		 		break;
		}
		cnt++;
	}
}

int solve()
{
    for(int i = 1; i <= n; i++)
        if(!dfn[i])
            tarjan(i,i);
    return cnt;
}
int main()
{
    int i,j,k,kk,t,x,y,z;
    while(scanf("%d%d",&n,&m)!=EOF&&n)
    {
        init();
        for(i=0;i<m;i++)
        {
            scanf("%d%d",&x,&y);
            g[x].push_back(y);
            g[y].push_back(x);
        }
        solve();
        for(i=1;i<=n;i++)
            for(j=0;j<g[i].size();j++)
                if(belong[i]!=belong[g[i][j]])
                    a[belong[i]]++,a[belong[g[i][j]]]++;
        sum=0;
        for(i=1;i<cnt;i++)
            if(a[i]==2)
                sum++;
        sum++;
        printf("%d\n",sum/2);
    }
    return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值