在学习了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
#include
#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
#include
#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] root ? leaf : root);
}
return 0;
}
个人愚昧观点,欢迎指正与讨论