poj1236 - Network of Schools(连通图--tarjan算法)

这道题就是tarjan的简单应用

大牛博客

上面是tarjan算法的详解

总结一下精华就是寻找循环点 ,利用栈和深搜寻找循环点

搜索的过程中如果找到某点,并且点在栈中,那么说明找到一个循环了,那么这个循环就是栈中点前面的所有点

比如:栈中元素从底到头为  :5  3 6  4 1   然后深搜的时候又搜到了3,这时候说明 6 4 1 3 是一个连通分量    

因为6 4 1 3 必定是一个环 (这里的环必须是可以从头走到尾的环)       

回归到这一题,虽然是一个简单应用,但是其实也并不好想

1.第一问,选择最少的点可以遍历这N个点。原来想着,这还不简单,算一下所有点的出度和入度

入度为零,说明没有点可以到达这点,需要的点数加一。简单!!

但是发现,如果是一个环,那么输出答案是0,但是实际答案是1,如果是两个环,输出答案应该是2

只要带环,这样就不成立的

但是发现输出的答案和环的个数有关,此处的环即一个连通分量,由于每个连通分量中任意两点可以互达

所以现在看每个连通分量是否连接,那么把每一个连通分量当做一个点。这时候再计算每一个点的入度(此时的情况中不会包括环了),最后数一数入度为0的点的个数,那么就是答案

2.第二问,用最少的边将这个图变为连通图,即每两个点两两互达

对于这个图,想让两两互达,最少的情况就是把其连成环,没有比这更少的情况了

例如,对于四个点 1 2 3 4 让其连成环这样让其两两相连用的边数最少

那么对于每一个连通分量,其已经连环,那么看成一个点就OK

然后这样,每个点目前都为成环,成为环的必要条件就是没有入度和出度为0的点

这样求一下入度和出度, 找两者最大的输出,这样可以保证连接后入度和出度没有为0的点

这样就可以连成环,成为连通图了

注意!!!当点为1的时候要特判!


#include<iostream>
#include<stdio.h>
#include<queue>
#include<string.h>
using namespace std;
const int maxn=100+10;
int line[maxn];
int dfn[maxn],low[maxn],color[maxn];
int in[maxn],out[maxn];//记录入度和出度
bool vis[maxn];
int e[maxn][maxn];
int dfs_num,col_num,top;
int ans=0;
int n;
void Tarjan(int x)
{
    dfn[x]=++dfs_num;
    low[x]=dfs_num ;
    vis[x]=1;//标记一下,表示已经入栈
    line [++top] = x ;//入栈
    for(int i=1 ; i<=n; i++)
    {
        if(e[x][i]==0)
            continue;
        int temp=i;
        if(!dfn[temp])
        {
            Tarjan(temp) ;
            low[x]=min(low[x],low[temp]) ;
        }
        else if(vis[temp])
            low[x]=min(low[ x ],dfn[temp]) ;
    }
    if(dfn[x]==low[x])  //构成强连通分量
    {
        vis[x]=false ;
        color[x]=++col_num ;//染色
        while(line[top]!=x)  //清空
        {
            color[line[top]]=col_num ;
            vis [line[top--]]=false ;
        }
        top--;
    }
}
void slove()
{
    memset(dfn,0,sizeof(dfn));
    memset(vis,0,sizeof(vis));
    memset(low,0,sizeof(low));
    memset(color,0,sizeof(color));
    dfs_num=0,col_num=0,top=0;
    for(int i=1; i<=n; i++)
    {
        if(dfn[i]==0)
        {
            Tarjan(i);
        }
    }
}
int main()
{
    scanf("%d",&n);
    int m;
    memset(e,0,sizeof(e));
    for(int i=1; i<=n; i++)
    {
        int num=0;
        while(~scanf("%d",&m)&&m)
        {
            e[i][m]=1;
        }
    }
    slove();
//    for(int i=1; i<=n; i++)
//    {
//        for(int j=1; j<=n; j++)
//        {
//            printf("%d ",e[i][j]);
//        }
//        printf("\n");
//    }
//    for(int i=1; i<=n; i++)
//    {
//        printf("%d ",color[i]);
//    }
//    printf("\n");
    memset(in,0,sizeof(in));
    memset(out,0,sizeof(out));
    int in_num=0,out_num=0;
    for(int i=1; i<=n; i++)
    {
        for(int j=1; j<=n; j++)
        {
            if(e[i][j]&&color[i]!=color[j])
            {
                in[color[j]]++;
                out[color[i]]++;
            }
        }
    }
    for(int i=1; i<=col_num; i++)
    {
        if(!in[i])in_num++;
        if(!out[i])out_num++;
    }
    if(col_num==1)
        printf("1\n0\n");
    else
        printf("%d\n%d\n",in_num,max(in_num,out_num));
}





                                                                                                                                                                                                                                                                                                                   



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值