tarjan求缩点--P2746 [USACO5.3]校园网Network of Schools

一些概念:

  1. 强连通 :在有向图\(G\)中,两个顶点 \(u\) ,\(v\),如果存在一条路径 \(u\)\(v\) 且存在一条路径\(v\)\(u\),则这两个顶点是强连通的

  2. 强连通图 :如果有向图\(G\)中的每两个顶点都强连通,那么称\(G\)是一个强连通图

  3. 极大强连通子图\(G\)是一个极大强连通子图,当且仅当\(G\)是一个强连通图,且其中不存在其他强连通图

  4. 强连通分量 :有向非强连通图的极大强连通子图称为强连通分量

结论 :若将有向图中所有的强连通分量缩成一个点,则原图会形成一个\(DAG\)(有向无环图)

Tarjan算法:

比较重要的两个数组的定义

\(dfn_u\)为结点\(u\)搜索的次序编号(时间戳)不会改变,\(low_u\)\(u\)\(u\)的子树(经过最多一条后向边或栈中横叉边)能够回溯到的最早的栈中结点的次序号

则当结点\(u\)的搜索过程结束后,若\(dfn_u = low_u\),则以\(u\)为根的搜索子树上所有还在栈中的结点是一个强连通分量。

算法流程

  1. 因为\(tarjan\)图不一定联通,所以我们每次从时间戳未更新的点开始\(dfs\)

  2. 链前按顺序将点入栈,搜到一个强连通分量出栈,直到恰好能回溯到它本身的点出栈为止

  3. 对每一个强连通分量染色,重新建图,变成了一个\(DAG\)

例题

这道题对于任务\(A\),求必须接受新软件副本的最少学校数目(子任务 A),两两联通的学校只需要选择一个就可以了,所以两两联通的学校可以合并,由此想到了缩点,缩点之后形成了\(DAG\),于是任务\(A\)我们要求的就是缩点后入度为0的点,因为他无法通过其他强连通分量到达

对于任务\(B\),计算最少需要增加几个扩展,使得不论我们给哪个学校发送新软件,它都会到达其余所有的学校,也就是把一个非强连通图变成一个强连通图,也就是缩点后入度为0和出度为0的块取个max

code

#include <iostream>
#include <algorithm>
#include <cstdio>
using namespace std;
int read(){
	int x = 1,a = 0;char ch = getchar();
	while (ch < '0'||ch > '9'){if (ch == '-') x = -1;ch = getchar();}
	while (ch >= '0'&&ch <= '9'){a = a*10+ch-'0';ch = getchar();}
	return x*a;
}
const int maxn = 1e5+10;
int n;
struct node{
	int to,next;
}ed[maxn];
int head[maxn],tot;
void add(int u,int to){
	ed[++tot].to = to;
	ed[tot].next = head[u];
	head[u] = tot;
}
int dfn[maxn],low[maxn],st[maxn],top,cnt,color,col[maxn];
void tarjan(int x){
	dfn[x] = low[x] = ++cnt;
	st[++top] = x;
	for (int i = head[x];i;i = ed[i].next){
		int to = ed[i].to;
		if (!dfn[to]){
			tarjan(to);
			low[x] = min(low[x],low[to]);
		}
		else if (!col[to]) low[x] = min(low[x],dfn[to]);
	}
	if (dfn[x] == low[x]){
		color++;
		int y;
		while (y = st[top--]){
			col[y] = color;
			if (x == y) break;
		}
	}
}
int maxx,minn;
int in[maxn],out[maxn];
int main(){
	n = read();
	for (int i = 1;i <= n;i++){
		int x;
		while (~scanf ("%d",&x)&&x != 0) add(i,x);
	}
	for (int i = 1;i <= n;i++){
		if (!dfn[i]) tarjan(i);
	}
	if (color == 1){printf("1\n0\n");return 0;}
	for (int i = 1;i <= n;i++){
		for (int j = head[i];j;j = ed[j].next){
			int to = ed[j].to;
			if (col[to] != col[i]) in[col[to]]++,out[col[i]]++;
		}
	}
	for (int i = 1;i <= color;i++){
		if (in[i] == 0) maxx++;
		if (out[i] == 0) minn++;
	}
	printf("%d\n",maxx);
	printf("%d\n",max(maxx,minn));
	return 0;
}
posted @ 2020-11-09 19:14  小又又yyyy  阅读(96)  评论(0编辑  收藏  举报