强连通分量

tarjan

基于深度优先搜索,用于寻找有向图中的连通块。
主要代码如下:

inline void tarjan(int x){
	dfn[x]=low[x]=++idx;
	vis[x]=1;
	sta[++top]=x;
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].v;
		if(!dfn[v]){//还未访问过这个节点
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}
		else if(vis[v]==1){//这个节点在栈中
			low[x]=min(low[x],dfn[v]);
		}
	}
	if(dfn[x]==low[x]){//找到一个强联通分量
		++cnt;
		while(x!=sta[top]){
			vis[sta[top]]=0;
			qlt[sta[top]]=cnt;//染色,标记这个节点属于第几号强联通分量
			siz[cnt]++;//记录这一个连通块的节点数
			top--;
		}
		vis[x]=0;//还要将这个节点弹出来
		qlt[x]=cnt;
		siz[cnt]++;
		top--;
	}
}

如果是要遍历整张图找到所有的连通块,通常还要加这句

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

因为图中不一定全部都是联通的。
每一个点只访问了一次,所以tarjan的时间复杂度是O(n)的。
通常还要用到tarjan缩点。缩点的时候将全部边遍历一次,
如果两个节点不属于同一个连通块,那么就连一条边,为了避免这种情况:
a->c b->c a,b属于同一个连通块。
可以用并查集处理一下,避免重复将两个相同的连通块连边。(重复连边好像也并没有什么影响)

题目


USACO 2003 Fall:
popular cow
这道题首先要知道:对于一个有向无环图,出度为零的点,从其他任意一个点都可以到达这个点。
所以我们只需要将原图用tarjan缩点变成一个有向无环图,找到出度为零的点,输出那个连通块的大小就行了。

#include<bits/stdc++.h>
#define cg c=getchar()
#define N 10020
#define M 50050
using namespace std;
template<typename xxx>inline void read(xxx &x){
	x=0;int f=1;char cg;
	while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
	x*=f;
}
template<typename xxx>inline void print(xxx x){
	if(x<0) x=-x,putchar('-');
	if(x>9) print(x/10);
	putchar(x%10+48);
}
int n,m,aa,bb;
struct node{
	int v,nxt;
}e[M];
int head[N];
int tot;
inline void add(int a,int b){
	++tot;
	e[tot].v=b;
	e[tot].nxt=head[a];
	head[a]=tot;
}
int idx;
int dfn[N];
int low[N];
int sta[N];
int vis[N];
int top;
int cnt;
int qlt[N];
int siz[N];
int cd[N];
inline void tarjan(int x){
	dfn[x]=low[x]=++idx;
	vis[x]=1;
	sta[++top]=x;
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].v;
		if(!dfn[v]){
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}
		else if(vis[v]==1){
			low[x]=min(low[x],dfn[v]);
		}
	}
	if(dfn[x]==low[x]){
		++cnt;
		while(x!=sta[top]){
			vis[sta[top]]=0;
			qlt[sta[top]]=cnt;
			siz[cnt]++;
			top--;
		}
		vis[x]=0;
		qlt[x]=cnt;
		siz[cnt]++;
		top--;
	}
}
int main(){
	read(n);read(m);
	for(int i=1;i<=m;i++){
		read(aa);read(bb);
		add(aa,bb);
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]) tarjan(i);
	}
	//进行缩点
	for(int i=1;i<=n;i++){
		for(int j=head[i];j;j=e[j].nxt){
			int v=e[j].v;
			if(qlt[i]!=qlt[v]){
				cd[qlt[i]]++;//因为不用重建一张图,只需要记录出度就行了
			}
		}
	}
	//因为重构的图也有可能存在两个连通块之间没有边连接,它们的出度也为零,所以要考虑这种情况
	int ans=0;
	int id;
	for(int i=1;i<=cnt;i++){
		if(cd[i]==0){
			ans++;
			id=i;
		}
	}
	if(ans==1){//只能有一个出度为零的连通块
		print(siz[id]);
	}
	else print(0);
}

IOI 1996 网络协议
这道题有两个问题,第一个问题还是比较明显,就是缩点后看有几个入度为0的点,每一个入度为零的点都必须放消息。
第二问就相当于将缩点后的图变成一个连通块。每一个入度为零的边都要增加一条入度,每一个出度为零的边都要增加一条出度。最后取较大值。
有个小细节,如果缩点后只有一个点,那么就不用加边了,所以要特判一下。
code:

#include<bits/stdc++.h>
#define N 120
#define M 13000
#define cg c=getchar()
using namespace std;
template<typename xxx>inline void read(xxx &x){
	x=0;int f=1;char cg;
	while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
	x*=f;
} 
template<typename xxx>inline void print(xxx x){
	if(x<0) x=-x,putchar('-');
	if(x>9) print(x/10);
	putchar(x%10+48);
}
struct node{
	int u,v,nxt;
}e[M],e2[M];
int head[N];
int tot;
inline void add(int a,int b){
	++tot;
	e[tot].u=a;
	e[tot].v=b;
	e[tot].nxt=head[a];
	head[a]=tot;
}
int n;
int aa;
int idx;
int top;
int dfn[N];
int low[N];
int vis[N];
int sta[N];
int qlt[N];
int cnt;
inline void tarjan(int x){
	dfn[x]=low[x]=++idx;
	vis[x]=1;
	sta[++top]=x;
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].v;
		if(!dfn[v]) {
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}
		else if(vis[v]==1){
			low[x]=min(low[x],dfn[v]);
		}
	}
	if(dfn[x]==low[x]){
		++cnt;
		while(x!=sta[top]){
			qlt[sta[top]]=cnt;
			vis[sta[top]]=0;
			top--;
		}
		qlt[x]=cnt;
		vis[x]=0;
		top--;
	}
} 
int rd[N];
int cd[N];
int ans;
int main(){
//	freopen("protocols10.in","r",stdin);
	read(n);
	for(int i=1;i<=n;i++){
		while(true){
			read(aa);
			if(aa==0) break;
			add(i,aa);
		}
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]) tarjan(i);
	}
	for(int i=1;i<=tot;i++){
		int x=e[i].u;
		int y=e[i].v;
		if(qlt[x]!=qlt[y]) {
			rd[qlt[y]]++;
			cd[qlt[x]]++;
		}
	}
	int ans1=0;
	int ans2=0;
	for(int i=1;i<=cnt;i++){
		if(rd[i]==0){
			ans1++;
			ans++;
		}
		if(cd[i]==0){
			ans2++;
		} 
	}
	print(ans);putchar('\n');
	if(cnt==1) print(0);//特判 只有一个连通块,不需要加边。
	else print(max(ans1,ans2));
}

消息的传递:
跟上一题的第一问一样。。。只不过输入变成了邻接矩阵。
code:

#include<bits/stdc++.h>
#define cg c=getchar()
#define N 1200
#define M N*(N-1)/2
using namespace std;
template<typename xxx>inline void read(xxx &x){
	x=0;int f=1;char cg;
	while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
	x*=f;
} 
template<typename xxx>inline void print(xxx x){
	if(x<0) x=-x,putchar('-');
	if(x>9) print(x/10);
	putchar(x%10+48);
}
struct node{
	int u,v,nxt;
}e[M];
int head[N];
int tot;
inline void add(int a,int b){
	++tot;
	e[tot].u=a;
	e[tot].v=b;
	e[tot].nxt=head[a];
	head[a]=tot;
} 
int n;
int aa;
/*--------------------------*/
int idx;
int dfn[N];
int low[N];
int vis[N];
int sta[N];
int top;
int cnt;
int qlt[N];
inline void tarjan(int x){
	dfn[x]=low[x]=++idx;
	sta[++top]=x;
	vis[x]=1;
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].v;
		if(!dfn[v]){
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}
		else if(vis[v]==1){
			low[x]=min(low[x],dfn[v]);
		}
	}
	if(dfn[x]==low[x]){
		++cnt;
		while(x!=sta[top]){
			qlt[sta[top]]=cnt;
			vis[sta[top]]=0;
			top--;
		}
		qlt[x]=cnt;
		vis[x]=0;
		top--;
	}
}
int rd[N];
int main(){
	read(n);
	for(int i=1;i<=n;i++){
		for(int j=1;j<=n;j++){
			read(aa);
			if(aa==1){
				add(i,j);
			}
		}
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]) tarjan(i);
	}
	for(int i=1;i<=tot;i++){
		int x=e[i].u;
		int y=e[i].v;
		if(qlt[x]!=qlt[y]){
			rd[qlt[y]]++;
		}
	}
	int ans=0;
	for(int i=1;i<=cnt;i++){
		if(rd[i]==0){
			ans++;
		}
	}
	print(ans);
}

间谍网络:
这道题跟之前几道差别不大,缩点统计入度就AC了。(我WA了十几次)
code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
template<class T>
inline void Read(T &x)
{
	x=0; int s=1; char ch=getchar();
	while(ch<'0' || ch>'9') {if(ch=='-') s=-1; ch=getchar();}
	while(ch>='0' && ch<='9'){x=x*10+ch-'0'; ch=getchar();} x*=s;
}
template<class T>
void print(T x)
{
	if(x==0) return;
	print(x/10);
	putchar(x%10+'0');
}
struct node{
	int to,next;
};
const int N=3e4+10;
node edge[N<<1];
int len,h[N];
int n,p,val[N],r,in[N],out[N];
int indx,low[N],dfn[N],s[N],top,f[N],cnt,val2[N];
bool visited[N];
void add(int x,int y)
{
	edge[++len].to=y; edge[len].next=h[x];
	h[x]=len;
}
void tarjan(int u)
{
	dfn[u]=low[u]=++indx; s[++top]=u; visited[u]=true;
	
	for(int i=h[u]; i; i=edge[i].next)
	{
		int to=edge[i].to;
		
		if(!dfn[to])
		{
			tarjan(to);
			low[u]=min(low[to],low[u]);
		}
		else if(visited[to]) low[u]=min(dfn[to],low[u]);
	}
	
	if(dfn[u]==low[u])
	{
		int v=-1; cnt++;
		
		do{
			v=s[top--]; f[v]=cnt; val2[cnt]=min(val2[cnt],val[v]);
			visited[v]=false;
		}while(u!=v);
	}
}
void init()
{
	memset(val,0x3f,sizeof(val));
	memset(val2,0x3f,sizeof(val2));
	scanf("%d%d",&n,&p);
	for(int i=1; i<=p; i++)
	{
		int id,money; Read(id); Read(money);
		val[id]=money;
	}
	Read(r);
	for(int i=1; i<=r; i++){
		int x,y; Read(x); Read(y);
		add(x,y); in[y]++; out[x]++;
	}
	bool flag=false;
	for(int i=1; i<=n; i++)
	if(val[i]!=0x3f3f3f3f && out[i]!=0) flag=true;
	
	if(!flag)
	{
		printf("NO\n%d\n",1); return;
	}
	for(int i=1; i<=n; i++){
		if(!in[i] && val[i]==0x3f3f3f3f){
			puts("NO");
			printf("%d\n",i);
			exit(0);
		}
	}
	memset(in,0,sizeof(in));
	for(int i=1; i<=n; i++)
	if(!dfn[i]) tarjan(i);
	for(int u=1; u<=n; u++)
	for(int i=h[u]; i; i=edge[i].next){
		int to=edge[i].to;
		if(f[u]!=f[to]) in[f[to]]++;
	}
	int ans=0;
	for(int i=1; i<=cnt; i++)
	if(!in[i]) ans+=val2[i];
	printf("YES\n%d",ans);
}
int main(){
	init();
	return 0;
}

APIO 2009 抢掠计划
这道题还是比较好想,先tarjan缩点后新建一张图跑最长路就行了。(不知道大佬有没有其他更简单的做法)
code:

#include<bits/stdc++.h>
#define N 500004
#define inf 0x3f3f3f3f
#define cg c=getchar()
using namespace std;
template<typename xxx>inline void read(xxx &x){
	x=0;int f=1;char cg;
	while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
	x*=f;
}
template<typename xxx>inline void print(xxx x){
	if(x<0) x=-x,putchar('-');
	if(x>9) print(x/10);
	putchar(x%10+48);
}
struct node{
	int u,v,nxt;
}e[N],e2[N];
int head[N];
int tot;
inline void add(int a,int b){
	++tot;
	e[tot].v=b;
	e[tot].u=a;
	e[tot].nxt=head[a];
	head[a]=tot;
}
int n;
int m;
int aa,bb;
int s,p;
int val[N];
int w[N];
int pub[N];
int jg[N];
/*-------------------------------*/
int idx;
int dfn[N];
int low[N];
int st[N];
int top;
int vis[N];
int cnt;
int qlt[N];
inline void tarjan(int x){
	dfn[x]=low[x]=++idx;
	st[++top]=x;
	vis[x]=1;
	for(int i=head[x];i;i=e[i].nxt){
		int v=e[i].v;
		if(!dfn[v]){
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}
		else if(vis[v]==1){
			low[x]=min(low[x],dfn[v]);
		}
	}
	if(dfn[x]==low[x]){
		++cnt;
		while(x!=st[top]){
			qlt[st[top]]=cnt;
			vis[st[top]]=0;
			w[cnt]+=val[st[top]];
			if(pub[st[top]]==1) jg[cnt]=1;
			top--;
		}
		qlt[x]=cnt;
		vis[x]=0;
		w[cnt]+=val[x];
		if(pub[x]==1) jg[cnt]=1;
		top--;
	}
}
/*---------------------------*/
int head2[N];
int tot2;
inline void add2(int a,int b){
	++tot2;
	e2[tot2].u=a;
	e2[tot2].v=b;
	e2[tot2].nxt=head2[a];
	head2[a]=tot2;
}
/*-0------------------------------*/
int dis[N];
queue<int>q;
inline void spfa(){
	for(int i=1;i<=cnt;i++){
		dis[i]=0;vis[i]=0;
	}
	s=qlt[s];
	q.push(s);vis[s]=1;dis[s]=w[s];
	while(!q.empty()){
		int x=q.front();
		q.pop();
		vis[x]=0;
		for(int i=head2[x];i;i=e2[i].nxt){
			int v=e2[i].v;
			if(dis[v]<dis[x]+w[v]){
				dis[v]=dis[x]+w[v];
				if(!vis[v]){
					vis[v]=1;
					q.push(v);
				}
			}
		}
	}
}
int main(){
	read(n);read(m);
	for(int i=1;i<=m;i++){
		read(aa);read(bb);
		add(aa,bb);
	}
	for(int i=1;i<=n;i++) read(val[i]);
	read(s);read(p);
	for(int i=1;i<=p;i++){
		read(aa);
		pub[aa]=1;
	}
	for(int i=1;i<=n;i++){
		if(!dfn[i]) tarjan(i);
	}
	for(int i=1;i<=m;i++){
		int x=e[i].u;
		int y=e[i].v;
		if(qlt[x]!=qlt[y]){
			add2(qlt[x],qlt[y]);
		}
	}
	spfa();
	int ans=-inf;
	for(int i=1;i<=cnt;i++){
		if(jg[i]==1){
			ans=max(ans,dis[i]);
		}
	}
	print(ans);
}

POI 2001 和平委员会

2-sat问题,推荐博客:
https://blog.csdn.net/JarjingX/article/details/8521690
https://blog.sengxian.com/algorithms/2-sat

code:

#include<bits/stdc++.h>
#define cg c=getchar()
#define N 30000
#define M 30000
using namespace std;
template<typename xxx>inline void read(xxx &x){
	x=0;int f=1;char cg;
	while(c<'0'||c>'9'){if(c=='-')f=-1;cg;}
	while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+(c^48);cg;}
	x*=f;
} 
template<typename xxx>inline void print(xxx x){
	if(x<0) x=-x,putchar('-');
	if(x>9) print(x/10);
	putchar(x%10+48);
}
vector<int>e[N];
int n,m;
int aa,bb;
inline int other(int x){
	if(x%2==0) return x-1;
	return x+1; 
}
int idx;
int dfn[N];
int low[N];
int st[N];
int top;
int cnt;
int qlt[N];
int vis[N];
inline void tarjan(int x){
	dfn[x]=low[x]=++idx;
	st[++top]=x;
	vis[x]=1;
	for(int i=0;i<e[x].size();i++){
		int v=e[x][i];
		if(!dfn[v]) {
			tarjan(v);
			low[x]=min(low[x],low[v]);
		}
		else if(vis[v]==1){
			low[x]=min(low[x],dfn[v]);
		}
	}
	if(dfn[x]==low[x]){
		++cnt;
		while(x!=st[top]){
			qlt[st[top]]=cnt;
			vis[st[top]]=0;
			top--;
		}
		qlt[x]=cnt;
		vis[x]=0;
		top--;
	}
}
int main(){
	read(n);read(m);
	for(int i=1;i<=m;i++){
		read(aa);read(bb);
		e[aa].push_back(other(bb));
		e[bb].push_back(other(aa));
	}
	for(int i=1;i<=(n<<1);i++){
		if(!dfn[i]) tarjan(i);
	}
	for(int i=1;i<=n;i++){
		int x=2*i-1;
		int y=2*i;
		if(qlt[x]==qlt[y]){
			printf("NIE");
			return 0;
		}
	}
	for(int i=1;i<=n;i++){
		int x=2*i-1;
		int y=2*i;
		if(qlt[x]<qlt[y]){
			print(x);putchar(' ');
		}
		else{
			print(y);putchar(' ');
		}
	}
}

心得

  • 有向无环图中一个出度为零的点,从任意一个点出发都可到达它。
  • 有向无环图至少有一个入度为零的点。
  • 有向无环图的生成树个数等于入度非零的节点的入度积。
  • 强联通分量的题好像多数与入度出度有关,想题的时候可以往这方面考虑。
posted @ 2019-09-14 16:24  JayTwenty  阅读(278)  评论(0编辑  收藏  举报