zoukankan      html  css  js  c++  java
  • Network of Schools(强连通分量缩点(邻接表&矩阵))

    Description

    A number of schools are connected to a computer network. Agreements have been developed among those schools: each school maintains a list of schools to which it distributes software (the “receiving schools”). Note that if B is in the distribution list of school A, then A does not necessarily appear in the list of school B
    You are to write a program that computes the minimal number of schools that must receive a copy of the new software in order for the software to reach all schools in the network according to the agreement (Subtask A). As a further task, we want to ensure that by sending the copy of new software to an arbitrary school, this software will reach all schools in the network. To achieve this goal we may have to extend the lists of receivers by new members. Compute the minimal number of extensions that have to be made so that whatever school we send the new software to, it will reach all other schools (Subtask B). One extension means introducing one new member into the list of receivers of one school.

    Input

    The first line contains an integer N: the number of schools in the network (2 <= N <= 100). The schools are identified by the first N positive integers. Each of the next N lines describes a list of receivers. The line i+1 contains the identifiers of the receivers of school i. Each list ends with a 0. An empty list contains a 0 alone in the line.

    Output

    Your program should write two lines to the standard output. The first line should contain one positive integer: the solution of subtask A. The second line should contain the solution of subtask B.

    Sample Input

    5
    2 4 3 0
    4 5 0
    0
    0
    1 0
    

    Sample Output

    1
    2
    


    这题给了一个有向图。
     
    需要解决两个问题:
    第一是需要给多少个点,才能传遍所有点。
     
    第二问是加多少条边,使得整个图变得强连通。
     
    使用Tarjan进行缩点,得到一个SCC图、
     
    这个图有多少个入度为0的,多少个出度为0的。
     
    假设有n个入度为0,m个出度为0
     
    那么第一个答案就是n,第二个答案是max(n,m)
     
     
    具体证明不解释了,貌似以前做过的题目,有解释。
     
    需要注意的是假如只有一个强连通分量,即整个图是连通的,那么第一个答案是1,第二个答案是0


    开始用桥来判断是不是同一个连通分量,结果果断错了,其实下图应该就会出错


    原因是通过头插法先遍历3,结果3的出度为0,由于2通向3已经访问过,因此不能在访问,因此2-->3的路没有标志cut,没法统计这天边的出入度情况,因此出度为0的变为2个了,正确答案应该是1个,所以错了,不能企图通过桥来算出出度入度

    错误代码:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #define MOD 100000
    #define inf 1<<29
    #define LL long long
    #define MAXN 20010
    #define MAXM = 50010
    using namespace std;
    struct Edge
    {
        int to,next;
        bool cut;
    } edge[MAXN];
    
    
    int head[MAXN],tot;
    int low[MAXN],DFN[MAXN],belong[MAXN];///belong 的值为1-block
    int index,top,fenzhiNum;
    int block ;  ///强连通分量
    bool inStack[MAXN];
    int bridgeNum;  ///桥的数目
    int stack[MAXN];
    int vis[MAXN];
    int inans,outans;
    int outdu[MAXN];
    int indu[MAXN];
    
    void addedge(int u,int v)
    {
        edge[tot].to = v;
        edge[tot].next = head[u];
        edge[tot].cut = false;
        head[u] = tot++ ;
    }
    void ini(){
        index = block = top = fenzhiNum = 0;
        inans = 0, outans = 0 ;
        memset(DFN,0,sizeof(DFN));
        memset(inStack,false,sizeof(inStack));
        memset(vis,0,sizeof(vis));
        memset(outdu,0,sizeof(outdu));
        memset(indu,0,sizeof(indu));
    }
    void Tarjan(int u)
    {
        vis[u] = true;
        int v;
        low[u] = DFN[u] = ++index;
        stack[top++] = u;
        inStack[u] = true;
        for(int i=head[u] ; i!=-1 ; i=edge[i].next)
        {
            v = edge[i].to;
            //if( v == pre ) continue;    ///因为是无向图,所以两条是双向的,所以只遍历一条就够了
            if( !DFN[v] )
            {
                Tarjan(v );
                if(low[u]>low[v])
                    low[u] = low[v];
               if(low[v] > DFN[u] ){
                    bridgeNum++;
                    edge[i].cut = true;
                    //edge[i^1].cut = true;  ///将两条双向边都设置为桥
                }
    
            }
            else if( inStack[v] && low[u] > DFN[v])
                low[u] = DFN[v];
        }
        if(low[u] == DFN[u])
        {
            block++;
            do
            {
                v=stack[--top];  ///清空当前强连通分量栈 必须清空
                inStack[v] = false;
                belong[v]=block;   ///v节点都编号为block  也就是这是一个块
            }
            while(v!=u);
        }
    }
    
    void solve(int N)
    {
        ini();
        for(int i=1;i<=N;i++)
            if(!vis[i])
                Tarjan(i);
        for(int i=1; i<=N ; i++){  ///缩点
            for(int j=head[i] ; j!=-1 ; j=edge[j].next)
              if( edge[j].cut)//belong[i]!=belong[ edge[j].to ])//edge[j].cut )
                indu[ belong[ edge[j].to ] ]++,outdu[ belong[i] ]++ ;
        }
        for(int i=1;i<=block ;i++)
            if(indu[i] == 0)
               inans++;
        for(int i=1;i<=block ;i++)
            if(outdu[i] == 0)
               outans++;
       // printf("indu=%d,outdu=%d
    ",inans,outans);
       if(block == 1) printf("1
    0
    ");
       else printf("%d
    %d
    ",inans,max(inans,outans));
        //printf("%d
    ",(ans+1)/2 );
    }
    
    int main ()
    {
        int n,m;
        while(~scanf("%d",&n))
        {
            int u,v,mark=0;
            tot=0;
            memset(head,-1,sizeof(head));
            for(int i=1; i<=n; i++)
            {
                while(scanf("%d",&u)&&u!=0){
                  mark=0;
                  for(int j=head[i] ; j!=-1 ; j=edge[j].next)  ///去重边
                    if(edge[j].to == u){
                        mark = 1;
                        break;
                    }
                  if(!mark) addedge(i,u);
                }
           }
            solve(n);
        }
        return 0;
    }
    


    正确代码矩阵:

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #define MAXV 110
    #define min(a,b) (a>b?b:a)
    #define max(a,b) (a>b?a:b)
    
    int n,map[MAXV][MAXV],outdegree[MAXV],indegree[MAXV];
    int dfn[MAXV];									//第一次访问的步数
    int low[MAXV];									//子树中最早的步数
    int stap[MAXV],stop;							//模拟栈
    bool instack[MAXV];								//是否在栈中
    int count;										//记录连通分量的个数
    int cnt;										//记录搜索步数
    int belong[MAXV];								//属于哪个连通分量
    
    void init(){
    	count=stop=cnt=0;
    	memset(instack,false,sizeof(instack));
    	memset(map,0,sizeof(map));
    	memset(dfn,0,sizeof(dfn));
    }
    
    void tarjan(int x){
    	int i;
    	dfn[x]=low[x]=++cnt;
    	stap[stop++]=x;
    	instack[x]=true;
    	for(i=1;i<=n;i++){
    		if(!map[x][i]) continue;
    		if(!dfn[i]){
    			tarjan(i);
    			low[x]=min(low[i],low[x]);
    		}else if(instack[i])
    			low[x]=min(dfn[i],low[x]);
    		//与x相连,但是i已经被访问过,且还在栈中
            //用子树节点更新节点第一次出现的时间
    	}
    
    	if(low[x]==dfn[x]){
    		count++;
    		while(1){
    			int tmp=stap[--stop];
    			belong[tmp]=count;
    			instack[tmp]=false;
    			if(tmp==x) break;
    		}
    	}
    }
    
    void output(){
    	int i,j,inzero=0,outzero=0;
    	for(i=1;i<=n;i++){
    		indegree[i]=outdegree[i]=0;
    	}
    	for(i=1;i<=n;i++)				//找连通分量入度与出度
    		for(j=1;j<=n;j++)
    			if(map[i][j] && belong[i]!=belong[j]){
    				indegree[belong[j]]++;
    				outdegree[belong[i]]++;
    			}
    	for(i=1;i<=count;i++){			//找入度与出度为0的点
    		if(!indegree[i]) inzero++;
    		if(!outdegree[i]) outzero++;
    	}
    	
    
    	if(count==1)					//只有1个结点要特判
    		printf("1
    0
    ");
    	else
    		printf("%d
    %d
    ",inzero,max(inzero,outzero));
    }
    
    int main(){
    	int i,a;
    	while(~scanf("%d",&n)){
    		init();
    		for(i=1;i<=n;i++){
    			while(scanf("%d",&a) && a) map[i][a]=1;
    		}
    		for(i=1;i<=n;i++)
    			if(!dfn[i])	tarjan(i);
    		output();
    	}
    	return 0;
    }



    正确代码邻接表

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<cmath>
    #include<algorithm>
    #define MOD 100000
    #define inf 1<<29
    #define LL long long
    #define MAXN 20010
    #define MAXM = 50010
    using namespace std;
    struct Edge
    {
        int to,next;
        bool cut;
    } edge[MAXN];
    
    
    int head[MAXN],tot;
    int low[MAXN],DFN[MAXN],belong[MAXN];///belong 的值为1-block
    int index,top,fenzhiNum;
    int block ;  ///强连通分量
    bool inStack[MAXN];
    int bridgeNum;  ///桥的数目
    int stack[MAXN];
    int vis[MAXN];
    int inans,outans;
    int outdu[MAXN];
    int indu[MAXN];
    
    void addedge(int u,int v)
    {
        edge[tot].to = v;
        edge[tot].next = head[u];
        edge[tot].cut = false;
        head[u] = tot++ ;
    }
    void ini(){
        index = block = top = fenzhiNum = 0;
        inans = 0, outans = 0 ;
        memset(DFN,0,sizeof(DFN));
        memset(inStack,false,sizeof(inStack));
        memset(vis,0,sizeof(vis));
        memset(outdu,0,sizeof(outdu));
        memset(indu,0,sizeof(indu));
    }
    void Tarjan(int u)
    {
        vis[u] = true;
        int v;
        low[u] = DFN[u] = ++index;
        stack[top++] = u;
        inStack[u] = true;
        for(int i=head[u] ; i!=-1 ; i=edge[i].next)
        {
            v = edge[i].to;
            //if( v == pre ) continue;    ///因为是无向图,所以两条是双向的,所以只遍历一条就够了
            if( !DFN[v] )
            {
                Tarjan(v );
                if(low[u]>low[v])
                    low[u] = low[v];
                if(low[v] > DFN[u] ){
                    bridgeNum++;
                    edge[i].cut = true;
                    //edge[i^1].cut = true;  ///将两条双向边都设置为桥
                }
    
            }
            else if( inStack[v] && low[u] > DFN[v])
                low[u] = DFN[v];
        }
        if(low[u] == DFN[u])
        {
            block++;
            do
            {
                v=stack[--top];  ///清空当前强连通分量栈 必须清空
                inStack[v] = false;
                belong[v]=block;   ///v节点都编号为block  也就是这是一个块
            }
            while(v!=u);
        }
    }
    
    void solve(int N)
    {
        ini();
        for(int i=1;i<=N;i++)
            if(!vis[i])
                Tarjan(i);
        for(int i=1; i<=N ; i++){  ///缩点
            for(int j=head[i] ; j!=-1 ; j=edge[j].next)
              if( belong[i]!=belong[ edge[j].to ] )
                indu[ belong[ edge[j].to ] ]++,outdu[ belong[i] ]++ ;
        }
        for(int i=1;i<=block ;i++)
            if(indu[i] == 0)
               inans++;
        for(int i=1;i<=block ;i++)
            if(outdu[i] == 0)
               outans++;
       // printf("indu=%d,outdu=%d
    ",inans,outans);
       if(block == 1) printf("1
    0
    ");
       else printf("%d
    %d
    ",inans,max(inans,outans));
        //printf("%d
    ",(ans+1)/2 );
    }
    
    int main ()
    {
        int n,m;
        while(~scanf("%d",&n))
        {
            int u,v,mark=0;
            tot=0;
            memset(head,-1,sizeof(head));
            for(int i=1; i<=n; i++)
            {
                while(scanf("%d",&u)&&u!=0){
                  mark=0;
                  for(int j=head[i] ; j!=-1 ; j=edge[j].next)  ///去重边
                    if(edge[j].to == u){
                        mark = 1;
                        break;
                    }
                  if(!mark) addedge(i,u);
                }
           }
            solve(n);
        }
        return 0;
    }
    










  • 相关阅读:
    stl 之 next_permutation 求出一个排序的下一个排列的函数 转载
    算法学习一~分治法~二分查找,快速的找~
    Cocos2d开发系列(九)
    每个好架构师都是一位出色的程序员
    本地通知和推送通知编程指南(2)
    【Cocos2d游戏开发之十二】浅析使用C++/C/OC进行iOS游戏混编出现“failed with exit”问题与小结;
    【Cocos2d游戏开发之十三】CCSprite利用Bezier(贝塞尔)做抛物线动作并让CCSprite同时播放两个Action动作!
    【Cocos2d游戏开发之八】开启高清(960*640)模式问题与解答、图片适配以及设置iphone横竖屏
    使用SQL 2008进行INSERT操作
    【Cocos2d游戏开发之十一】使用Box2d物理系统以及在cocos2d框架添加Box2d物理系统lib包的方法
  • 原文地址:https://www.cnblogs.com/zswbky/p/5432185.html
Copyright © 2011-2022 走看看