Tarjan求强连通分量

背景

  在昨天的NOIP模拟赛中,zyz与qhy出了一道tarjan的裸题,然而我不会就DFS了50分。最后虽然rank1但是分数很低。

算法描述

  略。

模板

  仅作者可见。

题目

CodeVS1332 上白泽慧音

裸题,统计一个size表示这个强联通分量的大小。

//tarjan求强连通分量 
#include <cstdio>
#include <algorithm>
#include <queue>
#include <vector>
#define maxn 110000
using namespace std;
int head[maxn], to[maxn], next[maxn], tid[maxn], tot, tim, size[maxn], s[maxn],
	dfn[maxn], low[maxn], vis[maxn], cnt, top;
priority_queue<int,vector<int>,greater<int> > heap;
void adde(int a, int b){to[++tot]=b;next[tot]=head[a];head[a]=tot;}
void tarjan(int pos)
{
	int p;
	dfn[pos]=low[pos]=++tim;
	s[++top]=pos;
	vis[pos]=1;
	for(p=head[pos];p;p=next[p])
	{
		if(vis[to[p]]==0)tarjan(to[p]);
		if(vis[to[p]]==1)low[pos]=min(low[pos],low[to[p]]);
	}
	if(dfn[pos]==low[pos])
		for(cnt++;s[top+1]!=pos;top--)
			tid[s[top]]=cnt, vis[s[top]]=2;
}
int main()
{
	int N, M, a, b, t, i;
	scanf("%d%d",&N,&M);
	for(i=1;i<=M;i++)
	{
		scanf("%d%d%d",&a,&b,&t);
		adde(a,b);if(t==2)adde(b,a);
	}
	for(i=1;i<=N;i++)if(vis[i]==0)tarjan(i);
	for(i=1;i<=N;i++)size[tid[i]]++;
	for(i=1,t=0;i<=N;i++)
		if(size[tid[i]]>size[tid[t]])t=i;
	for(i=1;i<=N;i++)if(tid[i]==tid[t])heap.push(i);
	printf("%d\n",heap.size());
	while(!heap.empty())printf("%d ",heap.top()),heap.pop();
	return 0;
}

CodeVS2822 爱在心中

就是说缩点之后问你有没有一个点它是所有点都能到达的,直接dfs就好了。

//tarjan求强连通分量
#include <cstdio>
#include <algorithm>
#define maxn 2000000
using namespace std;
int N, M, head[maxn], to[maxn], next[maxn], tot, tid[maxn], dfn[maxn], s[maxn], top,
	low[maxn], vis[maxn], size[maxn], tim, td[maxn], cnt, can[maxn];
void adde(int a, int b){to[++tot]=b;next[tot]=head[a];head[a]=tot;}
void tarjan(int pos)
{
	int p;
	dfn[pos]=low[pos]=++tim;
	s[++top]=pos;vis[pos]=1;
	for(p=head[pos];p;p=next[p])
	{
		if(vis[to[p]]==0)tarjan(to[p]);
		if(vis[to[p]]==1)low[pos]=min(low[pos],low[to[p]]);
	}
	if(dfn[pos]==low[pos])
		for(cnt++;s[top+1]!=pos;top--)
			tid[s[top]]=cnt,size[cnt]++,vis[s[top]]=2;
}
void dfs(int pos, int t)
{
	int p;
	vis[pos]=1;
	if(tid[pos]==t)can[tid[pos]]=1;
	for(p=head[pos];p;p=next[p])
	{
		if(vis[to[p]]==0)dfs(to[p],t);
		can[tid[pos]]=can[tid[pos]] or can[tid[to[p]]];
	}
}
int main()
{
	int i, a, b, x, j;
	scanf("%d%d",&N,&M);
	for(i=1;i<=M;i++)scanf("%d%d",&a,&b),adde(a,b);
	for(i=1;i<=N;i++)if(vis[i]==0)tarjan(i);
	for(x=cnt,i=1;i<=cnt;i++)x-=size[i]==1;
	printf("%d\n",x);
	x=0;
	for(i=1;i<=cnt;i++)
	{
		if(size[i]==1)continue;
		for(j=1;j<=cnt;j++)can[j]=0;
		for(j=1;j<=N;j++)vis[j]=0;
		for(j=1;j<=N;j++)if(vis[j]==0)dfs(j,i);
		for(j=1;j<=cnt and can[j];j++);
		if(j>cnt)
		{
			x=1;
			for(j=1;j<=N;j++)if(tid[j]==i)printf("%d ",j);
		}
	}
	if(x==0)printf("-1\n");
	return 0;
}

luoguP2746 [USACO5.3]校园网Network of Schools

缩点之后统计所有点的入度出度,第一问就是输出入度为0的点的个数。

第二问输出max(入度为0的点的个数,出度为0的点的个数)+连通块个数

对于第二问:对于一张有向无环图,把入度为0的点的集合叫做{S},那出度为0的点的集合叫做{T},那么以{S}中的每一个点为起点DFS,肯定能遍历{T}中的所有点。那么我们想要把这张图变成一个环。首先考虑card(S)<card(T)的情况,这时任意t∈{T},一定对应至少一个s∈{S},满足s能到达t,于是我们就连一条边t->s,那么s与t就形成了一个环,对于每一个t连出一条这样的边,那么总边数就是card(T),这个时候,如果原先的图是一个连通块,答案就是card(T),否则还要加上连通块个数。当card(S)>=card(T)时同理。

//Tarjan求强连通分量
#include <cstdio>
#include <algorithm>
#define maxn 100000
using namespace std;
int N, M, head[maxn], to[maxn], next[maxn], rd[maxn], tid[maxn], tim, cnt, dfn[maxn],
	low[maxn], etot, top, s[maxn], vis[maxn], cd[maxn];
void adde(int a, int b){to[++etot]=b;next[etot]=head[a];head[a]=etot;}
void tarjan(int pos)
{
	int p;
	dfn[pos]=low[pos]=++tim;
	s[++top]=pos;vis[pos]=1;
	for(p=head[pos];p;p=next[p])
	{
		if(vis[to[p]]==0)tarjan(to[p]);
		if(vis[to[p]]==1)low[pos]=min(low[pos],low[to[p]]);
	}
	if(dfn[pos]==low[pos])
		for(cnt++;s[top+1]!=pos;top--)
			tid[s[top]]=cnt,vis[s[top]]=2;
}
void shrink()
{
	int p, i;
	for(i=1;i<=N;i++)
		for(p=head[i];p;p=next[p])
			if(tid[i]^tid[to[p]])
				cd[tid[i]]++,rd[tid[to[p]]]++;
}
void input()
{
	int i, t;
	scanf("%d",&N);
	for(i=1;i<=N;i++)for(scanf("%d",&t);t;scanf("%d",&t))adde(i,t);
}
void print()
{
	int cnt1=0, cnt2=0, i;
	for(i=1;i<=cnt;i++)cnt1+=rd[i]==0,cnt2+=cd[i]==0;
	printf("%d\n%d",cnt1,max(cnt1,cnt2));
}
int main()
{
	input();
	for(int i=1;i<=N;i++)if(!vis[i])tarjan(i);
	shrink();
	if(cnt==1){printf("1\n0\n");return 0;}
	print();
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值