Tarjan算法

Tarjan算法与无向图连通性

缩点

int tdfn,c[N],dfn[N],low[N],tak[N];
bool ins[N];
void tarjan(int u)
{
	dfn[u]=low[u]=++tdfn;
	tak[++tak[0]]=u;
	ins[u]=1;//在栈里
	
	for(int i=Tail[u];i;i=E[i].pre) 
	{
		int v=E[i].v;
		
		if(!dfn[v]) {tarjan(v);low[u]=min(low[u],low[v]);}
		else if(ins[v]) low[u]=min(low[u],dfn[v]);
	}
	
	if(low[u]==dfn[u])
	{
		++c[0];
		while(tak[tak[0]+1]!=u) c[tak[tak[0]]]=c[0],ins[tak[tak[0]--]]=0;
	}
}

Grass Cownoisseur(有向图任意跑一条反边的最长路)

(grass.cpp/1s/128M)
题目描述
约翰有 块草场,编号 到 ,这些草场由若干条单行道相连。奶牛贝西是美味牧草的鉴赏家,她想到
达尽可能多的草场去品尝牧草。
贝西总是从 号草场出发,最后回到 号草场。她想经过尽可能多的草场,贝西在通一个草场只吃一次
草,所以一个草场可以经过多次。因为草场是单行道连接,这给贝西的品鉴工作带来了很大的不便,贝
西想偷偷逆向行走一次,但最多只能有一次逆行。请问贝西最多能吃到多少个草场的牧草。

Tarjan缩点建新图。

所有的可以从x出发又回到x的点都和x缩成一个节点,故新图没有节点能从x出发还回到x点,即x不可能被算两次。

#include<bits/stdc++.h>
using namespace std;

const int N=1e5+5;
int n,m,ans;
int tE,te,Tail[N],tail[N*2];
struct e_
{
	int v,pre;
}E[N],e[N*3];
inline void add_(int u,int v)
{
	E[++tE]=(e_){v,Tail[u]};Tail[u]=tE;
}
inline void add(int u,int v)
{
	e[++te]=(e_){v,tail[u]};tail[u]=te;
	e[++te]=(e_){v+N,tail[u+N]};tail[u+N]=te;
	e[++te]=(e_){u+N,tail[v]};tail[v]=te;
}

int tdfn,c[N],dfn[N],low[N],tak[N],siz[N*2];
bool ins[N];
void tarjan(int u)
{
	dfn[u]=low[u]=++tdfn;
	tak[++tak[0]]=u;
	ins[u]=1;//在栈里
	
	for(int i=Tail[u];i;i=E[i].pre) 
	{
		int v=E[i].v;
		
		if(!dfn[v]) 
		{
			tarjan(v);
			low[u]=min(low[u],low[v]);
		}
		else if(ins[v]) low[u]=min(low[u],dfn[v]);
	}
	
	if(low[u]==dfn[u])
	{
		++c[0];
		while(tak[tak[0]+1]!=u)
		siz[c[0]]++,
		c[tak[tak[0]]]=c[0],
		ins[tak[tak[0]--]]=0;
		siz[c[0]+N]=siz[c[0]];
	}
}

void build()
{
	for(int i=1;i<=n;++i) 
	if(!dfn[i]) tarjan(i);
	
	for(int i=1;i<=n;++i)
	for(int j=Tail[i];j;j=E[j].pre)
	{
		int v=E[j].v;
		if(c[i]==c[v]) continue;	
		add(c[i],c[v]);
	}
}

int D[N*2];
bool vis[N*2];
void spfa()
{
	deque<int>q;
	q.push_back(c[1]);

	while(!q.empty())
	{
		int u=q.front();
		q.pop_front();
		vis[u]=0;
		for(int i=tail[u];i;i=e[i].pre)
		{
			int v=e[i].v,w=siz[v];
			
			if(D[v]<D[u]+w)
			{
				D[v]=D[u]+w;
				if(!vis[v]) vis[v]=1,q.push_back(v);
			}
		}
	}
}
int main()
{
//	freopen("grass.in","r",stdin);
//	freopen("grass.out","w",stdout);
	
	scanf("%d %d",&n,&m);
	for(int i=1,u,v;i<=m;++i) scanf("%d %d",&u,&v),add_(u,v);
	build();//GCC新图 
	
	spfa();//分层图最长路跑一跑 
	
	printf("%d",max(D[c[1]],D[c[1]+N]));
}

割边判定

1.对于每个点,dfn[x]表示其dfs序, low[x]表示从树以外的边可到的dfs序最小的点。

2.对于每条边\((u,v)\),如果dfn[u]<low[v],即从v无法到达u之前的节点,即只有该边能将u,v连接,去掉该边图必定会分裂。

注意:由于可能出现重边,所以每次dfs时记录进来的边,不走这条边,走和这条边相重叠的边是可以的。

void tarjan(int u,int fa)
{
	dfn[u]=low[u]=++tdfn;
	
	for(int i=tail[u];i;i=e[i].pre)
	{
		int v=e[i].v;
		if(!dfn[v])
		{
			tarjan(v,i);
			low[u]=min(low[u],low[v]);
			
			if(dfn[u]<low[v]) bri[i]=bri[i^1]=1;
		}
		else if(i!=(fa^1)) low[u]=min(low[u],dfn[v]);
	}
}

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

网络

给定一张N个点M条边的无向连通图,然后执行Q次操作,每次向图中添加一条边,并且询问当前无向图中“桥”的数量。

输入格式
输入包含多组测试数据。

每组测试数据,第一行包含两个整数N和M。

接下来M行,每行包含两个整数A和B,表示点A和点B之间有一条边,点的编号为1~N。

接下来一行,包含整数Q。

在接下来Q行,每行包含两个整数A和B,表示在A和B之间加一条边。

当输入0 0时表示输入终止。

输出格式
每组数据第一行输出“Case x:”,其中x为组别编号,从1开始。

接下来Q行,每行输出一个整数,表示一次询问的结果。

每组数据输出完毕后,输出一个空行。

数据范围
1≤N≤100000
N−1≤M≤200000,
1≤A≠B≤N,
1≤Q≤1000
输入样例:

3 2
1 2
2 3
2
1 2
1 3
4 4
1 2
2 1
2 3
1 4
2
1 2
3 4
0 0

输出样例:

输出样例:

Case 1:
1
0

Case 2:
2
0
#include<bits/stdc++.h>
using namespace std;

const int N=1e5+5,M=2e5+5;
int n,m,k,te,tc,cas,tdfn,sum,c[N],tail[N],dfn[N],low[N],dep[N],pre[N];
bool bri[M<<1],vis[N];
struct e_
{
	int v,pre;
}e[M<<1];

void init()
{
	te=3;
	tc=tdfn=sum=0;
	
	memset(c,0,sizeof(c));
	memset(dfn,0,sizeof(dfn));
	memset(bri,0,sizeof(bri));
	memset(vis,0,sizeof(vis));
	memset(pre,0,sizeof(pre));
	memset(tail,0,sizeof(tail));
} 

inline void add(int u,int v)
{
	e[++te]=(e_){v,tail[u]};
	tail[u]=te;
}

void tarjan(int u,int fa)
{
	dfn[u]=low[u]=++tdfn;
	
	for(int i=tail[u];i;i=e[i].pre)
	{
		int v=e[i].v;
		
		if(!dfn[v])
		{		
			tarjan(v,i);
			
			low[u]=min(low[u],low[v]);
			
			if(dfn[u]<low[v]) sum++,bri[i]=bri[i^1]=vis[v]=1;
				
		}
		else if(i!=(fa^1)) low[u]=min(low[u],dfn[v]);
	}
}

void dfs(int u)
{
	c[u]=tc;
	
	for(int i=tail[u];i;i=e[i].pre)
	{
		int v=e[i].v;
		if(!c[v]&&!bri[i]) dfs(v);
	}
}

void dfs_(int u)
{
	for(int i=tail[u];i;i=e[i].pre)
	{
		int v=e[i].v;
		if(!pre[v]) 
		{
			dep[v]=dep[u]+1;
			pre[v]=u;
			dfs_(v);
		}
	}
}

void lca(int u,int v)
{
	if(dep[u]<dep[v]) swap(u,v);
	
	while(dep[u]>dep[v])
	{
		if(vis[u]) sum--,vis[u]=0;
		u=pre[u];
	}
	while(u!=v)
	{
		if(vis[u]) sum--,vis[u]=0;
		if(vis[v]) sum--,vis[v]=0;
		u=pre[u];
		v=pre[v];
	}
}
int main()
{
	while(~scanf("%d %d",&n,&m)&&n)
	{
	
		printf("Case %d:\n",++cas);

		init();
		for(int i=1,u,v;i<=m;++i)
		{
			scanf("%d %d",&u,&v);
			add(u,v);
			add(v,u);
		}
		
		for(int i=1;i<=n;++i) 
		if(!dfn[i]) tarjan(i,0);
		
		for(int i=1;i<=n;++i)
		if(!c[i])
		{
			++tc;
			dfs(i);
		}
		
		int tte=te;
		te=1;
		memset(vis,1,sizeof(vis));
		memset(tail,0,sizeof(tail));

		for(int i=4;i<=tte;++i)
		if(bri[i]) add(c[e[i].v],c[e[i^1].v]);
		
		
		dfs_(1);
		
		scanf("%d",&k);
		for(int i=1,a,b;i<=k;++i)
		{
			scanf("%d %d",&a,&b);
			if(c[a]!=c[b]) lca(c[a],c[b]);
			printf("%d\n",sum);
		}
		
		printf("\n");
	}
}

割点判定

1.对于每个点,dfn[x]表示其dfs序, low[x]表示从树以外的边可到的dfs序最小的点。

2.对于每组点\((u,v)\),如果dfn[u]<=low[v],即从v无法到达u之前的节点,即只有u能到v连接,去掉该点图必定会分裂。

对于根节点,当且仅当至少有两个点满足上述条件,即至少有两个子树时,根节点是割点。

void tarjan(int u)
{
	dfn[u]=low[u]=++tdfn;
	int cnt=0;
	for(int i=tail[u];i;i=e[i].pre)
	{
		int v=e[i].v;
		if(!dfn[v])
		{
			tarjan(v,i);
			low[u]=min(low[u],low[v]);
			
			if(dfn[u]<=low[v])
			{
				cnt++;
				if(u!=root||cnt>1) cut[u]=1;
			}
		}
		else low[u]=min(low[u],dfn[v]);
	}
}

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

B城

B城有 n 个城镇,m 条双向道路。

每条道路连结两个不同的城镇,没有重复的道路,所有城镇连通。

把城镇看作节点,把道路看作边,容易发现,整个城市构成了一个无向图。

输入格式
第一行包含两个整数 n 和 m。

接下来m行,每行包含两个整数 a 和 b,表示城镇 a 和 b 之间存在一条道路。

输出格式
输出共n行,每行输出一个整数。

第 i 行输出的整数表示把与节点 i 关联的所有边去掉以后(不去掉节点 i 本身),无向图有多少个有序点(x,y),满足 x 和 y 不连通。

数据范围
n≤100000,m≤500000
输入样例:

5 5
1 2
2 3
1 3
3 4
4 5

输出样例:

8
8
16
14
8
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e5+5,M=5e5+5;
ll n,root,tdfn,m,te,tail[N],dfn[N],low[N],ans[N],siz[N];
bool cut[N];
struct e_
{
	int v,pre;
}e[M<<1];
inline ll read()
{
	int res=0;
	char dd=getchar();
	while(dd<'0'||dd>'9') dd=getchar();
	while(dd>='0'&&dd<='9') res=(res<<1)+(res<<3)+dd-'0',dd=getchar();
	
	return res;
}
inline void add(int u,int v)
{
	e[++te]=(e_){v,tail[u]};
	tail[u]=te;
}

void tarjan(int u)
{
	int cntt=0;
	ll sum=0;
	
	siz[u]=1;
	dfn[u]=low[u]=++tdfn;
	
	for(int i=tail[u];i;i=e[i].pre)
	{
		int v=e[i].v;
		if(!dfn[v])
		{
			tarjan(v);
			
			siz[u]+=siz[v];
			low[u]=min(low[u],low[v]);
			
			if(dfn[u]<=low[v])
			{
				cntt++;
				ans[u]+=siz[v]*(n-siz[v]);
				sum+=siz[v];
				if(u!=root||cntt>1) cut[u]=1;
			}
		}
		else low[u]=min(low[u],dfn[v]);
	}
	if(cut[u]) ans[u]+=(n-sum-1)*(sum+1)+(n-1);
	else ans[u]=(n-1)*2;
}

int main()
{
	n=read();
	m=read();
	for(int i=1,u,v;i<=m;++i)
	{
		u=read();
		v=read();
		add(u,v);
		add(v,u);
	}
	
	for(int i=1;i<=n;++i)
		if(!dfn[i]){root=i;tarjan(i);}

	for(int i=1;i<=n;++i) printf("%lld\n",ans[i]);
}
posted @ 2020-10-23 20:11  林生。  阅读(132)  评论(0)    收藏  举报