一些概念:
-
强连通 :在有向图(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
0
");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
",maxx);
printf("%d
",max(maxx,minn));
return 0;
}