Tarjan(强连通分量 割点 缩点)

Tarjan(强连通分量 割点 缩点)

Tarjan 算法

  • 所需的变量
变量名
\(dfn[maxn]\) 当前节点是第几个被访问到的
\(low[maxn]\) 当前节点所能访问到的最小的dfn
\(sta[maxn]\) 存储可能构成强连通分量的栈
\(col[maxn]\) 记录各个节点所属于的强连通分量编号

强连通分量

  • 图中找到一个最大的图,使这个图中每个两点都能够互相到达。这个最大的图称为强连通分量,同时一个点也属于强连通分量。

缩点

把所有环按照染色情况缩成一个点,重新连边

P3387 【模板】缩点

首先链式前向星建原图,来一波tarjan缩点,然后再链式前向星建一个新图

最后来个topo求从入度为零的点到它能走到的点的最大值

缩点code

const int maxn = 2e5 + 10;
int n,m;
int a[maxn];
int dfn[maxn],head[maxn],cnt,col[maxn],sta[maxn],top,tot;
int dep,f[maxn],p,ans[maxn],in[maxn],low[maxn],vis[maxn];
std::vector<int> ed[maxn];
struct node{
	int v,next,u;
}e[maxn<<1];
void add(int u,int v) {
	e[++cnt].next = head[u];
	e[cnt].v = v;
	e[cnt].u = u;
	head[u] = cnt;
}
void tarjan(int u) {
	dfn[u] = low[u] = ++dep;
	vis[u] = 1;
	sta[++top] = u;
	for(int i = head[u]; i ;i = e[i].next) {
		int v = e[i].v;
		if(!dfn[v]) {
			tarjan(v);
			low[u] = min(low[u],low[v]);
		}
		else {
			if(vis[v])
			low[u] = min(low[u],low[v]);
		}
	}
	if(dfn[u] == low[u]) {
		col[u] = u;
		vis[u] = 0;
		while(sta[top] != u) {
			col[sta[top]] = u;
			a[u] += a[sta[top]];
			vis[sta[top--]] = 0;
		}
		top--;
	}
}
int topo() {
	queue<int> q;
	for(int i = 1; i <= n; i++) {
		if(!in[i] && col[i] == i) {
			q.push(i); 
			f[i] = a[i];
		}
	}
	while(!q.empty()) {
		int u = q.front();
		q.pop();
		for(int i = 0; i < ed[u].size(); i++) {
			int v = ed[u][i];
			f[v] = max(f[v],f[u]+a[v]);
			in[v]--;
			if(in[v] == 0) {
				q.push(v);
			}
		}
	}
	int sum = 0;
	for(int i = 1; i <= n; i++) {
		sum = max(sum,f[i]);
	}
	return sum;
}
int main() {
	cin >> n >> m;
	for(int i = 1; i <= n; i++) {
		cin >> a[i];
	}	
	for(int j = 1; j <= m; j++) {
		int u,v;
		cin >> u >> v;
		add(u,v);
	}
	for(int i = 1; i <= n; i++) {
		if(!dfn[i]) {
			tarjan(i);
		}
	}
	for(int i = 1; i <= m; i++) {
		int x = col[e[i].u];
		int y = col[e[i].v];
		if(x != y) {
			in[y]++;
			ed[x].PB(y);
		}
	}
	cout << topo() << "\n";
	return 0;
}

割点和割桥

对于无向图有双连通分量

  • 对于一个无向图,如果把一个点删除后这个图的极大连通分量数增加了,那么这个点就是这个图的割点(又称割顶)。
  • 对于一个无向图,如果删掉一条边后图中的连通分量数增加了,则称这条边为桥或者割边

使用\(Tarjan\) 判断是否为割点

  • 对于祖先,如果它有两个以上儿子,那么它必为割点
  • 对于某个顶点\(u\),如果存在\(v(u的儿子)\) 使得\(low[v] >= dfn[u]\) 那么\(u\)为割点

P3388 【模板】割点(割顶)

割点code

const int maxn = 2e5 + 10;

int n,m;
struct node{
	int v,next;
}e[maxn];
int head[maxn],cnt;
int dfn[maxn],low[maxn],ans[maxn];
int dep = 0,tot = 0;
void tarjan(int u,int fa) {
	low[u] = dfn[u] = ++dep;
	int an = 0;
	for(int i = head[u]; i; i = e[i].next) {
		int v = e[i].v;
		if(!dfn[v]) {
			tarjan(v,fa);
			low[u] = min(low[u],low[v]);
			if(u != fa && dfn[u] <= low[v]) {
				ans[u] = 1;
			}
			if(u == fa) {
				an++;
			}
		}
		low[u] = min(low[u],dfn[v]);
	}
	if(an >= 2 && u == fa) {
		ans[u] = 1;
	}
}
void add(int u,int v) {
	e[++cnt].next = head[u];
	e[cnt].v = v;
	head[u] = cnt;
}
int main() {
	cin >> n >> m;
	for(int i = 1; i <= m; i++) {
		int u,v;
		cin >> u >> v;
		add(u,v);
		add(v,u);
	}
	for(int i = 1; i <= n; i++) {
		if(dfn[i] == 0) {
			tarjan(i,i);
		}
	}
	for(int i = 1; i <= n; i++) {
		if(ans[i]) {
			tot++;
		}
	}
	cout << tot << "\n";
	for(int i = 1; i <= n; i++) {
		if(ans[i]) {
			cout << i << " ";
		}
	}
	cout << "\n";
	return 0;
}
posted @ 2021-04-06 14:41  EnthalpyDecreaser  阅读(94)  评论(0编辑  收藏  举报