对tarjan的一些理解

Posted ghosh

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了对tarjan的一些理解相关的知识,希望对你有一定的参考价值。

之前做tarjan的题,我一直没有搞清楚有向图和无向图中,代码的不同,今天下午向虎哥和zxk讨论了快一个小时,现在终于清楚些了。

最基本的一些东西

有向图

我们需要求的是强连通分量,在有向图中,有四种边。

技术图片
一种边为树枝边,从根节点遍历,每个节点第一次被访问到,即边(x,y)是从x到y是对y的第一次访问。这些边为树边,绿色表示
一种边为前向边,边(x,y)可以为表示x是y的祖先。蓝色表示。这种边对求scc没有影响,因为搜索树本来就存在从x到y的路径
一种边为后向边,边(x,y)可以表示y是x的祖先。黄色表示。这种边有用,这条边可以和搜索树上的从y到x的路径构成一个scc
一种边为横叉边,可以理解为连接到两个兄弟、堂兄弟等之间,二者不像前三个有祖先后裔的关系。红色表示。如果存在一条边可以从y到x的祖先,那么可以构成一个scc,否则这条边没用。
由于横叉边的存在,我们必须要判断当前点是否在栈内,如果在栈里说明v是u的父亲或祖先,不在栈里说明u和v属于不同分支。有可能我们从u通过横叉边访问到v,但是并不一定从v访问到u的祖先,也就是说虽然u和v通过一条边相连,但它们并不属于一个scc。如果是无向图,那肯定可以从v访问到u。

void Tarjan(int rt){
	dfn[rt] = low[rt] = ++dfn_cnt;
	ins[rt] = 1;
	for (int i = head[rt]; i; i = edge[i].next){
		int v = edge[i].to;
		if (!dfn[v]){
			Tarjan(v);
			low[rt] = min(low[rt], low[v]);
		}
		else if(ins[v]){//注意这里需要判断是否在栈内
			low[rt] = min(low[rt], dfn[v]);
		}
	}
}

无向图

我们需要求的是割点,点双;桥,边双。
有向图有横叉边,只能从u访问到不同分支的v,但不能从v访问到u
无向图没有横叉边,如果u能访问到v,那v肯定能访问到u
所以不需要判断是不是在栈内
void tarjan(int rt){
  dfn[rt] = low[rt] = ++dfn_cnt;
  int ch = 0, sum = 0;
  for (int i = head[rt]; i; i = edge[i].next){
  	int v = edge[i].to;
  	if (!dfn[v]){
  		Tarjan(v);
  		low[rt] = min(low[rt], low[v]);
  		ch++;
  		if ((rt == root && ch >= 2) || (rt != root && low[v] >= dfn[u]))
  			cut[rt] = 1;
  	}
  	else low[rt] = min(low[rt], dfn[v]);//不需要判断是否在栈内
  }
}

一道题

题目描述

Byteotia城市有n个 towns m条双向roads. 每条 road 连接 两个不同的 towns ,没有重复的road. 所有towns连通。

输入格式

输入n<=100000 m<=500000及m条边

输出格式

输出n个数,代表如果把第i个点去掉,将有多少对点不能互通。

样例输入

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

样例输出

8
8
16
14
8
对于一个非割点,删去会多出来2 * (n-1)对
对于一个割点,删去会有三部分,一是该点本身,二是该点的子树中可能会分成几部分,三是除去以该点为子树的所有节点后剩余部分,每部分相乘再相加即可。
通过这个题我发现对于一个割点u,并不是把它删去后,它所有子节点v都会满足dfn[u]<=low[v],只是一部分子节点会分开,还有一部分仍然属于一个点双。



#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
const int maxm = 5e5 + 5;
typedef long long ll;
struct Edge{
	int to, next;
}edge[maxm << 1];
int n, m, cnt, tot, dfn_cnt, head[maxn], dfn[maxn], low[maxn], ins[maxn], belong[maxn];
int num[maxn];
int in[maxn];
ll ans[maxn];
ll size[maxn], cut[maxn], root;
void Add(int u, int v){
	edge[++cnt].to = v;
	edge[cnt].next = head[u];
	head[u] = cnt;
}
void Tarjan(int rt){
	dfn[rt] = low[rt] = ++dfn_cnt;
	size[rt] = 1;
	int ch = 0, sum = 0;
	for (int i = head[rt]; i; i = edge[i].next){
		int v = edge[i].to;
		if (!dfn[v]){
			Tarjan(v);
			size[rt] += size[v];
			low[rt] = min(low[rt], low[v]);
			if (low[v] >= dfn[rt]){
				ch++;
				ans[rt] += (ll)size[v] * (n - size[v]);
				sum += size[v];
				if (rt != -1 || ch > 1) cut[rt] = 1;
			}
		}
		else low[rt] = min(low[rt], dfn[v]);
	}
	if (cut[rt]) ans[rt] += n - 1 + (ll)(n - sum - 1) * (sum + 1);
	else ans[rt] += (n - 1) * 2;
}
int main(){
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= m; i++){
		int u, v;
		scanf("%d%d", &u, &v);
		if (u == v) continue;
		Add(u, v);
		Add(v, u);
	}
	Tarjan(1);
	for (int i = 1; i <= n; i++)
		cout << ans[i] << endl;
}


















以上是关于对tarjan的一些理解的主要内容,如果未能解决你的问题,请参考以下文章

如何理解这段代码片段中的两对括号?

对求有向图强连通分量的tarjan算法原理的一点理解

模板强连通分量和tarjan算法

luogu3387 模板缩点 [tarjan 缩点]

Tarjan算法详解

Tarjan离线求LCA