首页 > 代码库 > 强连通分量(学习心得)

强连通分量(学习心得)

定义:有向图强连通分量:在有向图G中,如果两个顶点vi,vj间(vi>vj)有一条从vi到vj的有向路径,同时还有一条从vj到vi的有向路径,则称两个顶点强连通如果有向图G的每两个顶点都强连通,称G是一个强连通图。有向图的极大强连通子图,称为强连通分量。

求强连通分量:

vector<int>pic[maxn];
int dfn[maxn],low[maxn],ans[maxn];
bool ins[maxn];
stack<int>st;
int dind=0,block=0;
int siz[maxn],cud[maxn];
void tarjan(int x)
{
	dind++;
	dfn[x]=low[x]=dind;
	ins[x]=true;
	st.push(x);
	for (int j=0;j<pic[x].size();j++)
	{
		int y=pic[x][j];
		if (dfn[y]==0)
		{
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if (ins[y]) low[x]=min(low[x],dfn[y]);
	}
	if (low[x]==dfn[x])
	{
		block++;
		siz[block]=0;
		while (true)
		{
			int thi=st.top();
			st.pop();
			ans[thi]=block;
			siz[block]++;
			ins[thi]=false;
			if (thi==x) break;
		}
	}
}

  例题1【UVA-11324 最大团】

技术分享

技术分享

分析:先计算强连通分量,然后建一张新图,每个点的权值是它所包含的点的数量,求图的最长链。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<stack>
using namespace std;
const int maxn=1010;
vector<int> pic[maxn],new_[maxn];
int dfn[maxn],low[maxn],ans[maxn];
bool ins[maxn];
stack<int>st;
int dind=0,block=0;
int siz[maxn],dp[maxn],Ans;
void tarjan(int x)
{
	dind++;
	dfn[x]=low[x]=dind;
	ins[x]=true;
	st.push(x);
	for (int j=0;j<pic[x].size();j++)
	{
		int y=pic[x][j];
		if (!dfn[y]){
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if (ins[y]) low[x]=min(low[x],dfn[y]);
	}
	if (low[x]==dfn[x])
	{
		block++;
		siz[block]=0;
		while (true)
		{
			int thi=st.top();
			st.pop();
			ans[thi]=block;
			siz[block]++;
			ins[thi]=false;
			if (thi==x) break;
		}
	}
}
int DP(int x){    
    if(dp[x]!=-1) return dp[x];
    int Max=0;  
    for(int i=0;i<new_[x].size();i++)  
        Max=max(Max,DP(new_[x][i]));  
    return dp[x]=siz[x]+Max;
}
void init(int n){
	for(int i=1;i<=n;i++) pic[i].clear(),new_[i].clear();
	memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low));
	memset(ans,0,sizeof(ans)); memset(ins,0,sizeof(ins));
	while(!st.empty()) st.pop(); dind=block=Ans=0;
	memset(siz,0,sizeof(siz)); memset(dp,-1,sizeof(dp));
}
int main(){
	int n,m,N,u,v;
	scanf("%d",&N);
	while(N--){
		scanf("%d%d",&n,&m);
		init(n);
		while(m--){
			scanf("%d%d",&u,&v);
			pic[u].push_back(v);
		}
		for(int i=1;i<=n;i++)
		if(!dfn[i]) tarjan(i);
		for(int i=1;i<=n;i++)
		for(int j=0;j<pic[i].size();j++)
		if(ans[i]!=ans[pic[i][j]]) new_[ans[pic[i][j]]].push_back(ans[i]);
		for(int i=1;i<=block;i++) Ans=max(Ans,DP(i));
		printf("%d\n",Ans);
	}
	return 0;
}

  例题2【POJ 1236 学校网络】

技术分享

第一问:缩点后求入度为0的结点数量;

第二问:对于一张有向无环图,可以通过把叶子结点连向根节点使它强连通;

like this:

技术分享

所以需要连的边数量为max(叶节点,根节点);

叶节点是出度为0的点,根节点是入度为0的点;

注意当只有一个强连通分量时,需要特判,为0;

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<vector>
#include<stack>
#include<set>
using namespace std;
const int maxn=110;
vector<int> pic[maxn];
int dfn[maxn],low[maxn],ans[maxn];
bool ins[maxn];
stack<int>st;
set<int>check[maxn];
int dind=0,block=0;
int siz[maxn];
int ans1,in[maxn],out[maxn];

void tarjan(int x)
{
	dind++;
	dfn[x]=low[x]=dind;
	ins[x]=true;
	st.push(x);
	for (int j=0;j<pic[x].size();j++)
	{
		int y=pic[x][j];
		if (!dfn[y]){
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if (ins[y]) low[x]=min(low[x],dfn[y]);
	}
	if (low[x]==dfn[x])
	{
		block++;
		siz[block]=0;
		while (true)
		{
			int thi=st.top();
			st.pop();
			ans[thi]=block;
			siz[block]++;
			ins[thi]=false;
			if (thi==x) break;
		}
	}
}
int main(){
	int n,i,j;
	scanf("%d",&n);
	for(i=1;i<=n;i++){
		scanf("%d",&j);
		while(j) pic[i].push_back(j),scanf("%d",&j);	
	}
	for(int i=1;i<=n;i++)
		if(!dfn[i]) tarjan(i);
	for (i=1;i<=n;i++)
		for (j=0;j<pic[i].size();j++){
			int b1=ans[i],b2=ans[pic[i][j]];
			if (b1==b2) continue;
			if (check[b1].count(b2)) continue;
			check[b1].insert(b2);
			in[b2]++; out[b1]++;
		}
	int root=0,leaf=0;
	for(int i=1;i<=block;i++){
		if(!in[i]) root++;
		if(!out[i]) leaf++;
	}
	printf("%d\n",root);
	if(block==1) printf("0");
	else printf("%d",max(leaf,root));
	return 0;
}

  类似的还有【HDU 2767】

很奇怪的是提交时出现了这样尴尬的问题:

0_0_20950778_21662.cpp
0_0_20950778_21662.cpp(29) : error C3861: “min”:  找不到标识符
0_0_20950778_21662.cpp(31) : error C3861: “min”:  找不到标识符
0_0_20950778_21662.cpp(83) : error C3861: “max”:  找不到标识符

然后在出错的库后面加上了#include "minmax.h",就过了?(莫名其妙)

代码:

#include<iostream>
#include "minmax.h" 
#include<cstdio>
#include<cstring>
#include<vector>
#include<stack>
#include<set>
using namespace std;
const int maxn=20010;
vector<int> pic[maxn];
int dfn[maxn],low[maxn],ans[maxn];
bool ins[maxn];
stack<int>st;
set<int>check[maxn];
int dind=0,block=0,siz[maxn];
int in[maxn],out[maxn],T;
void tarjan(int x)
{
	dind++;
	dfn[x]=low[x]=dind;
	ins[x]=true;
	st.push(x);
	for (int j=0;j<pic[x].size();j++)
	{
		int y=pic[x][j];
		if (!dfn[y]){
			tarjan(y);
			low[x]=min(low[x],low[y]);
		}
		else if (ins[y]) low[x]=min(low[x],dfn[y]);
	}
	if (low[x]==dfn[x])
	{
		block++;
		siz[block]=0;
		while (true)
		{
			int thi=st.top();
			st.pop();
			ans[thi]=block;
			siz[block]++;
			ins[thi]=false;
			if (thi==x) break;
		}
	}
}
void init(int n){
	for(int i=1;i<=n;i++)  pic[i].clear(),check[i].clear();;
	memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low));
	memset(ans,0,sizeof(ans)); memset(ins,0,sizeof(ins));
	while(!st.empty()) st.pop(); 
	dind=0;block=0; memset(siz,0,sizeof(siz));
	memset(in,0,sizeof(in)); memset(out,0,sizeof(out));
}
int main(){
	scanf("%d",&T);
	while(T--){
		int n,i,j,m;
		scanf("%d%d",&n,&m);
		init(n);
		for(i=1;i<=m;i++){
			int x,y;
			scanf("%d%d",&x,&y);
			pic[x].push_back(y);	
		}
		for(int i=1;i<=n;i++)
			if(!dfn[i]) tarjan(i);
		for (i=1;i<=n;i++)
			for (j=0;j<pic[i].size();j++){
				int b1=ans[i],b2=ans[pic[i][j]];
				if (b1==b2) continue;
				if (check[b1].count(b2)) continue;
				check[b1].insert(b2);
				in[b2]++; out[b1]++;
			}
		int root=0,leaf=0;
		for(int i=1;i<=block;i++){
			if(!in[i]) root++;
			if(!out[i]) leaf++;
		}
		if(block==1) printf("0\n");
		else printf("%d\n",max(leaf,root));
	}
	return 0;
}

  ————————————————————————————————————————————————————————

 来自Paper Cloud的博客,未经允许,请勿转载,谢谢

强连通分量(学习心得)