在学习了tarjan算法求解强连通分量之后就接触到强连通缩点,但是就是不知道怎么运用tarjan算法来找缩点,后来接触了几个有关缩点的题目,才了解到缩点的关键所在;
对于一个图,我们进行强连通分量求解之后,进行缩点操作,缩点的最大好处在于把一个杂乱无章的有向图变成一个有向无环图,而在有向无环图中,有两种点比较特殊:一种是入度为 0 的点,另一种是 出度为 0 的点。我们把入度为0的点就叫做根,出度为0的点叫做叶子,可以参见下图
对于图中的绿色点就是叶子,红色的点就是根,未标记的就是中间点,但是注意:这里是缩点之后形成的有向无环图,图中的每一个点就是一个强连通分量,那么我们怎么把一个图缩点成为一个有向无环图呢?那么这里就用到一个数组来“染色”,我们在求强连通分量的时候有一个 Bcnt 用来记录强连通分量个数,有一个数组Belong[MAX]来染色,Belong[i] =Bcnt 就表示 i 这个元素属于第Bcnt个强连通分量,在把所有的点求完强连通分量之后,我们只是得到一个所有点的归属数组Belong[MAX],那么,之后我们就用两个数组 in[MAX],out[MAX]表示缩点后的有向无环图的点的入度和出度,用以下代码求解缩点形成的图
memset(in,0,sizeof(in)); memset(out,0,sizeof(out)); for(i = 1; i <= n; i ++) { for(int k = head[i]; k != -1; k = edge[k].next) { j = edge[k].to; if(Belong[i] != Belong[j])//注意这里就是所属的强连通分量,也就是属于哪一个缩点 out[Belong[i]] ++, in[Belong[j]] ++; } }那么我们就得到这个有向无环图的关系,那么下面给出两个有关缩点的题目
poj2186Popular cow
题意:有N头牛,M个关系,每个关系为 a b,表示牛a认为牛b 收欢迎,那么问根据所给信息判断有多少头牛是收到所有的牛的欢迎,而且这里a认为b受欢迎,b认为c受欢迎,那么a也会认为c受欢迎,
分析:这里我们根据M个关系建图,然后对于这个图缩点,那么,根据上面的缩点分析,我们只要求叶子的个数,因为别的缩点都指向叶子,叶子处在最高层,就是最受欢迎的,那么我们根据所有缩点的out[i]=0的个数,判断,如果只有一个,那么这个缩点(强连通分量)的牛都是最受欢迎的,(这里注意,叶子数为0就是表示只有一个强连通分量,那么所有的牛都是最受欢迎的),如果有多个就输出0
那么直接上马,马上在来点注释:
// 708K 47MS #include<stdio.h> #include<string.h> #define MAX 10001 int N,M; //建图用到的 struct Edge { int to,next; }edge[MAX*5]; int head[MAX],tol; void add(int a,int b) { edge[tol].to = b; edge[tol].next = head[a]; head[a] = tol++; } //tarjan int Belong[MAX],dfn[MAX],low[MAX],stack[MAX],time,Bcnt,top; bool instack[MAX]; int out[MAX];//记录出度 void tarjan(int u) { int v; dfn[u] = low[u] = ++time; stack[top ++] = u; instack[u] = true; for(int j = head[u]; j != -1; j = edge[j].next) { v = edge[j].to; if(!dfn[v]) { tarjan(v); if(low[v] < low[u]) low[u] = low[v]; } else if(instack[v] && dfn[v] < low[u]) low[u] = dfn[v]; } if(dfn[u] == low[u]) { Bcnt ++; do { v = stack[--top]; instack[v] = false; Belong[v] = Bcnt; }while(u != v); } } int main() { int u,v; int a,b;//输入要用到的a,b scanf("%d%d",&N,&M); // 建图 tol = 0; memset(head,-1,sizeof(head)); while(M --) { scanf("%d%d",&a,&b); add(a,b); } //tarjan memset(dfn,0,sizeof(dfn)); Bcnt = top = time = 0; for(u = 1; u <= N; u ++) if(!dfn[u]) tarjan(u); //tarjan之后求缩点的出度 memset(out,0,sizeof(out)); for(u = 1; u <= N; u ++) { for(int j = head[u]; j != -1; j = edge[j].next) { v = edge[j].to; if(Belong[u] != Belong[v]) out[Belong[u]] ++; } } int num = 0,ans = 0,flag;//num记录缩点后出度为0的个数,flag记录入度为0的缩点标号(也就是第几个个联通分量) for(int i = 1; i <= Bcnt; i ++) if(out[i] == 0) num ++,flag = i; if(num == 1)//只有一个联通分量 { for(int i = 1; i <= N; i ++) if(Belong[i] == flag)//这里计算属于这个连通分量的所有牛 ans ++; printf("%d\n",ans); } else printf("0\n"); return 0; }hdu2767
题意:这里有一个东西要你证明,就是有n个式子,用1到n标记,有m个关系,每个关系为a b 表示a推导出b,那么我们要这n个式子都是等价的最少还需要多少个关系
分析:这里我们把n个式子看做n个点,那么要是n个点等价就是任意一个点能推导出任意的另一个点,意思就是最后要是一个强连通,问最少要添加多少条边,我们根据已有的关系建图之后,强连通缩点,然后我们分别求叶子和根的数量,那么最多的那个就是我们要的答案,但是当缩点只有一个点的时候,答案是 0
上马
//125MS 1652K #include<stdio.h> #include<string.h> #define MAX 10001 int n,m; //前向星 struct Edge { int to,next; }edge[MAX*5]; int head[MAX*2],tol; void add(int a,int b) { edge[tol].to = b; edge[tol].next = head[a]; head[a] = tol ++; } //tarjan算法 int dfn[MAX*2],low[MAX*2],stack[MAX*2],Belong[MAX*2],time,top,Bcnt; bool instack[MAX*2]; int in[MAX*2],out[MAX*2];//记录缩点后的入度出度 void tarjan(int u) { int v; dfn[u] = low[u] = ++time; stack[top ++] = u; instack[u] = true; for(int j = head[u] ; j != -1; j = edge[j].next) { v = edge[j].to; if(!dfn[v]) { tarjan(v); if(low[v] < low[u]) low[u] = low[v]; } else if(instack[v] && dfn[v] <low[u]) low[u] = dfn[v]; } if(dfn[u] == low[u]) { Bcnt ++; do { v = stack[-- top]; instack[v] = false; Belong[v] = Bcnt; }while(v != u); } } int main() { int a,b,i,j; int T; scanf("%d",&T); while(T --) { scanf("%d%d",&n,&m); //建图 tol = 0; memset(head,-1,sizeof(head)); while(m --) { scanf("%d%d",&a,&b); add(a,b); } //缩点 Bcnt = time = top = 0; memset(dfn,0,sizeof(dfn)); for(i = 1; i <= n; i ++) if(!dfn[i]) tarjan(i); if(Bcnt == 1) { printf("0\n");continue; } memset(in,0,sizeof(in)); memset(out,0,sizeof(out)); for(i = 1; i <= n; i ++) { for(int k = head[i]; k != -1; k = edge[k].next) { j = edge[k].to; if(Belong[i] != Belong[j]) out[Belong[i]] ++, in[Belong[j]] ++; } } //缩点之后找叶子和根的数量 int root = 0,leaf = 0; for(i = 1; i <= Bcnt; i ++) { if(in[i] == 0) root ++; if(out[i] == 0) leaf ++; } printf("%d\n",leaf > root ? leaf : root); } return 0; }个人愚昧观点,欢迎指正与讨论