Tarjan算法的练习

基本介绍:

Tarjan算法其实就是一种带技巧的DFS,比普通的dfs多了两个标记:dfn和low,实质上还是一种DFS。

Tarjan算法能做什么?

  1. 求有向图的强连通分量
  2. 求无向图的割点
  3. 求无向图的桥(割边)
  4. 求LCA(最近公共祖先)

网上有很多的tarjan算法详解,这里就不重复造轮子了。刚学tarjan算法的时候觉得很神奇,花了很长时间去消化,但是不去用慢慢就又忘了,所以这里贴出一些tarjan算法的练习题去巩固。

POJ - 1236 求强连通分量的练习

题目链接
题意是给你一张有向图,信息会沿着有向图的路径传播,
1.问要把初始信息给几个点才能传遍网络
2.问添加几条有向边可以让整张图强连通

题解:强连通缩点之后图变成DAG,入度为0的点的数量为第一问答案,入度为0的点和出度为0的点的数量的最大值为第二问答案。
AC代码:

#include
#include
#include
#include
#include
#include
using namespace std;
const int maxn = 233;
vector g[maxn];
struct node{
	int u,v,nxt;
}e[maxn*maxn];
int cnt;
int head[maxn];
void add(int u,int v){
	e[cnt].u = u;
	e[cnt].v = v;
	e[cnt].nxt = head[u];
	head[u] = cnt++;
}
int in[maxn],out[maxn];
int n;
void init(){
	cin>>n;
	cnt = 0;
	memset(head,-1,sizeof head);
	int u = 1,v;
	while(u <= n){
		while(~scanf("%d",&v)){
			if(v == 0) break;
			add(u,v);
		}
		u++;
	}
}
int id[maxn];
int dfn[maxn],low[maxn];
int index;
int scc;
stack s;
int inq[maxn];
void tarjan(int u){
	s.push(u);
	inq[u] = 1;
	low[u] = dfn[u] = index++;
	for(int i = head[u];i!=-1;i = e[i].nxt){
		int v = e[i].v;
		if(!dfn[v]) {
			tarjan(v);
			low[u] = min(low[u],low[v]);
		}
		else if(inq[v]) low[u] = min(low[u],dfn[v]);
	}
	if(low[u] == dfn[u]){
		scc++;
		while(s.top()!=u){
			int v = s.top();s.pop();
			id[v] = scc;
			inq[v] = 0;
		}
		s.pop();
		id[u] = scc;
		inq[u] = 0;
	}
}
void sol(){
	memset(dfn,0,sizeof dfn);memset(low,0,sizeof low);memset(inq,0,sizeof inq);
	memset(in,0,sizeof in);memset(out,0,sizeof out);
	index = 1;scc = 0;
	while(s.size()) s.pop();
	for(int i = 1;i <= n;++i) if(!dfn[i]) tarjan(i);
	if(scc == 1){
		cout<<1<<"\n"<<0;return;
	}
	for(int i = 0;i < cnt;++i){
		int u = e[i].u;
		int v = e[i].v;
		if(id[u] == id[v]) continue;
		in[id[v]]++;
		out[id[u]]++;
	}
	int a = 0,b = 0;
	for(int i = 1;i <= scc;++i){
		if(!in[i]) a++;
		if(!out[i]) b++;
	}
	cout<

HDU2586 求LCA的练习

题目链接
题意:一棵树,边权为1,查询两个结点之间的距离,多次查询
题解:先DFS求各点到根的距离,对于查询u,v,dis[u] + dis[v] - 2*dis[lca(u,v)]即为答案。
AC代码:

#include
#include
#define pb push_back
using namespace std;
typedef pair P;
const int maxn = 4e4 + 50;
int n,m;
vector

g[maxn],q[maxn]; vector

query; int lca[maxn]; void init(){ query.clear(); scanf("%d %d",&n,&m); for(int i = 0;i <= n;++i) g[i].clear(),q[i].clear(); for(int i = 1; i < n;++i){ int u,v,w; scanf("%d %d %d",&u,&v,&w); g[u].pb( P(v,w) ); g[v].pb( P(u,w) ); } for(int i = 0; i < m;++i) { int u,v;scanf("%d %d",&u,&v); query.pb( P(u,v) ); q[u].pb( P(v,i) ); q[v].pb( P(u,i) ); } } int vis[maxn]; int fa[maxn]; int fnd(int x){ if(x == fa[x]) return x; return fa[x] = fnd(fa[x]); } int dis[maxn]; void tarjan(int u,int w){ fa[u] = u; dis[u] = w; vis[u] = 1; for(int i = 0;i < g[u].size();++i){ int v = g[u][i].first; if(!vis[v]) tarjan(v,w + g[u][i].second),fa[v] = u; } for(int j = 0;j < q[u].size();++j){ int v = q[u][j].first; if(!vis[v]) continue; int id = q[u][j].second; lca[id] = fnd(v); } } void sol(){ for(int i = 0;i <= n;++i) fa[i] = i,vis[i] = dis[i] = 0; tarjan(1,0); for(int i = 0;i < query.size();++i){ int u = query[i].first; int v = query[i].second; printf("%d\n",dis[u] + dis[v] - 2*dis[lca[i]]); } } int main(){ int T;cin>>T; while(T--)init(),sol(); }

HDU4738 桥(割边)的练习

题目链接
题意:给一张无向图,求所有桥中权值最小的一个
题解:tarjan求桥,注意求出权值为0的话要输出1,因为要有人运炸弹

#include
#include
using namespace std;
const int maxn = 1e3 + 50;
const int inf = 0x3f3f3f3f;
int n,m;
struct node{
	int v,w,nxt;
}e[maxn*maxn];
int cnt = 0;
int head[maxn];
void add(int u,int v,int w){
	e[cnt].v = v;
	e[cnt].w = w;
	e[cnt].nxt = head[u];
	head[u] = cnt++;
}
void init(){
	cnt = 0;
	memset(head,-1,sizeof head);
	while(m--){
		int u,v,w;scanf("%d%d%d",&u,&v,&w);
		add(u,v,w);add(v,u,w);
	}
}
int dfn[maxn],low[maxn];
int index;
int ans = inf;
void tarjan(int u,int id){
	dfn[u] = low[u] = ++index;
	for(int i = head[u];i != -1;i = e[i].nxt){
		int v = e[i].v;
		if(!dfn[v]){
			tarjan(v,i);
			low[u] = min(low[u],low[v]);
			if(low[v] > dfn[u]) ans = min(ans,e[i].w);
		}
		else if(i != (id^1)) low[u] = min(low[u],dfn[v]);
	}
}
void sol(){
	memset(dfn,0,sizeof dfn);
	memset(low,0,sizeof low);
	index = 0;
	ans = inf;
	tarjan(1,-1);
	for(int i = 1;i <= n;++i){
		if(!dfn[i]){
			cout<<0<

POJ3694 割边 + LCA + 缩点 综合(并查集优化)

POJ3694
题意:给一张无向图,然后有q次操作,每次操作会添加一条无向边,每次操作完之后都要输出当前操作结束后的图的割边的个数。

题解:首先把边双连通分量通过并查集缩点,同时把割边标记出来并记录割边个数。缩点之后图变成了一棵树,然后操作的时候,就根据dfn,顺着两个点往上找到lca,沿途遇到的割边全都去掉,更新答案,顺便也用并查集缩一下点。仔细想想可以发现,当两个点往上爬到同一个并查集的时候,他们已经在同一个双连通分量了,再往上爬也不会有割边出现,所以可以直接跳出。(然而对于这题,一些优化不加也可以过
在这里插入图片描述
这是一开始的双连通分量没缩点,查询的时候缩点,用时1297ms

这是一开始双连通分量缩点了,查询的时候也缩点了,但是查询的时候直到爬到lca才结束,用时563ms
在这里插入图片描述
这是两个点爬到同一个并查集就结束更新,用时360ms
AC代码:

#include
#include
#include
#include
using namespace std;
const int maxn = 2e5 + 50;
const int inf = 0x3f3f3f3f;
int n,m,q;
struct node{
	int v,nxt;
}e[maxn*5];
int cnt = 0;
int head[maxn];
void add(int u,int v){
	e[cnt].v = v;
	e[cnt].nxt = head[u];
	head[u] = cnt++;
}
void init(){
	cnt = 0;
	for(int i = 0;i <=n ;++i) head[i] = -1;
	while(m--){
		int u,v;scanf("%d%d",&u,&v);
		add(u,v);add(v,u);
	}
}
int dfn[maxn],low[maxn],pre[maxn],inq[maxn],isgb[maxn];
int fa[maxn];// 用来缩点 
int fnd(int x){if(x == fa[x]) return x;return fa[x] = fnd(fa[x]);} 
int index;
int ans = 0;
void tarjan(int u,int id){
	dfn[u] = low[u] = ++index;
	inq[u] = 1;
	for(int i = head[u];i != -1;i = e[i].nxt){
		int v = e[i].v;
		if(i == (id^1)) continue;
		if(!dfn[v]){
			pre[v] = u;
			tarjan(v,i);
			if(low[v] > dfn[u]) isgb[v] = 1,ans++;
			else fa[v] = fnd(u);//缩点 
			low[u] = min(low[u],low[v]);
		}
		else if(inq[v]) low[u] = min(low[u],dfn[v]);
	}
	inq[u] = 0;
}
void link(int u,int v){
	int fx = fnd(u),fy = fnd(v);
	if(fx == fy) return ;
	fa[fx] = fy; return ;
}
void lca(int u,int v){
	while(dfn[u]!=dfn[v]){
		if(dfn[u] > dfn[v]){
			if(isgb[u]) ans--,isgb[u] = 0;
			link(u,pre[u]);//缩点 
			u = pre[u];
		}
		else {
			if(isgb[v]) ans--,isgb[v] = 0;
			link(v,pre[v]);//缩点 
			v = pre[v];
		}
		if(fnd(u) == fnd(v)) break;
	}
}
int ca = 0;
void sol(){
	for(int i = 0;i <= n;++i) isgb[i] = inq[i] = dfn[i] = low[i] = 0,fa[i] = i;
	index = 0;ans = 0;
	tarjan(1,-1);
	int q;cin>>q;
	printf("Case %d:\n",++ca);
	while(q--){
		int u,v;scanf("%d %d",&u,&v);
		int ru = fnd(u);
		int rv = fnd(v);
		if(ru != rv){
			lca(u,v);
		}
		printf("%d\n",ans);
	}
	printf("\n");
}
int main(){
	while(~scanf("%d %d",&n,&m)){
		if(n == 0 && m == 0) break;
		init();
		sol();
	}
} 

这题求LCA部分网上很多写法是先预处理所有点的深度,操作u和v的时候先提升到同一个深度再一起往上跑,但是其实我们可以利用dfn的性质直接找到lca。

你可能感兴趣的:(图论)