tarjan求缩点--P2746 [USACO5.3]校园网Network of Schools
一些概念:
-
强连通 :在有向图\(G\)中,两个顶点 \(u\) ,\(v\),如果存在一条路径 \(u\) 到 \(v\) 且存在一条路径\(v\)到\(u\),则这两个顶点是强连通的
-
强连通图 :如果有向图\(G\)中的每两个顶点都强连通,那么称\(G\)是一个强连通图
-
极大强连通子图 :\(G\)是一个极大强连通子图,当且仅当\(G\)是一个强连通图,且其中不存在其他强连通图
-
强连通分量 :有向非强连通图的极大强连通子图称为强连通分量
结论 :若将有向图中所有的强连通分量缩成一个点,则原图会形成一个\(DAG\)(有向无环图)
Tarjan算法:
比较重要的两个数组的定义 :
\(dfn_u\)为结点\(u\)搜索的次序编号(时间戳)不会改变,\(low_u\)为\(u\)或\(u\)的子树(经过最多一条后向边或栈中横叉边)能够回溯到的最早的栈中结点的次序号
则当结点\(u\)的搜索过程结束后,若\(dfn_u = low_u\),则以\(u\)为根的搜索子树上所有还在栈中的结点是一个强连通分量。
算法流程 :
-
因为\(tarjan\)图不一定联通,所以我们每次从时间戳未更新的点开始\(dfs\)
-
链前按顺序将点入栈,搜到一个强连通分量出栈,直到恰好能回溯到它本身的点出栈为止
-
对每一个强连通分量染色,重新建图,变成了一个\(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;
}