强连通分量大礼包

强连通分量大礼包

主要用基础题将一些有向图的强连通分量的题
难度递增,不过都水

[USACO06JAN]牛的舞会The Cow Prom

【题面】

约翰的N (2 <= N <= 10,000)只奶牛非常兴奋,因为这是舞会之夜!她们穿上礼服和新鞋子,别 上鲜花,她们要表演圆舞.
只有奶牛才能表演这种圆舞.圆舞需要一些绳索和一个圆形的水池.奶牛们围在池边站好, 顺时针顺序由1到N编号.每只奶牛都面对水池,这样她就能看到其他的每一只奶牛.
为了跳这种圆舞,她们找了 M条绳索.若干只奶牛的蹄上握着绳索的一端, 绳索沿顺时针方绕过水池,另一端则捆在另一些奶牛身上.这样,一些奶牛就可以牵引另一些奶 牛.有的奶牛可能握有很多绳索,也有的奶牛可能一条绳索都没有.
对于一只奶牛,比如说贝茜,她的圆舞跳得是否成功,可以这样检验:沿着她牵引的绳索, 找到她牵引的奶牛,再沿着这只奶牛牵引的绳索,又找到一只被牵引的奶牛,如此下去,若最终 能回到贝茜,则她的圆舞跳得成功,因为这一个环上的奶牛可以逆时针牵引而跳起旋转的圆舞. 如果这样的检验无法完成,那她的圆舞是不成功的.
如果两只成功跳圆舞的奶牛有绳索相连,那她们可以同属一个组合.
给出每一条绳索的描述,请找出,成功跳了圆舞的奶牛有多少个组合?

【思路】

模板题,我们只要统计点数大于1的强连通分量的个数即可。

【代码】

#include<bits/stdc++.h>
using namespace std;
const int  MAXN = 50005 ;
inline  int  read(){
    int s = 0 ; char g=getchar() ;while(g>'9'||g<'0')g=getchar() ;
    while(g>='0'&&g<='9')s=s*10+g-'0',g=getchar();return s ;
}
int head[ MAXN ] , to[ MAXN*2 ] , nex[ MAXN*2 ] , tot = 1 ;
int dfn[ MAXN ] , low[ MAXN ] , num = 0 , scc[ MAXN ];
int  N , M , root , kind = 0 ;
bool used[ MAXN ] ;
stack<int>q ; 
void  add( int x , int  y  ){
    to[ ++tot ] = y , nex[ tot ] = head[ x ] , head[ x ] = tot ;
}
void  tarjan( int u ){
    dfn[ u ] = low[ u ] = ++num ; used[ u ] =true ; 
    q.push( u ) ;
    int  flag = 0 ;
    for( int i = head[ u ] ; i ; i = nex[ i ] ){
        if( !dfn[ to[ i ] ] ){
            tarjan( to[ i ] ) ;
            low[ u ] = min( low[ u ] , low[ to[ i ] ] ) ;
        }
        else if( used[ to[ i ] ] ) low[ u ] = min ( low[ u ] ,dfn[ to[ i ] ] ) ;
    }
    if( low[ u ] == dfn[ u ] ){
        kind++ ;
        int  now = -1 ;
        while( now != u ){
            now = q.top() ; q.pop() ;
            used[ now ] = false ; 
            scc[ kind ]++ ;
        }
    }
}
int main(){
    N = read() , M = read() ;
    int  m1 , m2 ;
    for( int i = 1 ; i <= M ; ++i ){
        m1 = read() , m2 = read() ; if( m1 == m2 )continue ;
        add( m1 , m2 ) ;
    }
    for( int i = 1 ; i <= N ; ++i )
        if( !dfn[ i ] )root = i , tarjan( i ) ;
    int ans = 0 ;
    for( int i = 1 ; i <= kind ; ++i )
        if( scc[ i ] > 1 )ans++ ;
    cout<<ans ;
    return 0 ;
}

[HAOI2006]受欢迎的牛

【题面】

每头奶牛都梦想成为牛棚里的明星。被所有奶牛喜欢的奶牛就是一头明星奶牛。所有奶牛都是自恋狂,每头奶牛总是喜欢自己的。

奶牛之间的“喜欢”是可以传递的——如果A喜欢B,B喜欢C,那么A也喜欢C。牛栏里共有N 头奶牛,给定一些奶牛之间的爱慕关系。

请你算出有多少头奶牛可以当明星。

【思路】

模板题2,我们考虑缩点后,记录缩点后出度为0的点的个数sum,分类讨论

sum==1||sum>1 显然不存在 sum == 1 答案就是这个强联通分量中点的个数

【代码】

#include<bits/stdc++.h>
using namespace std;
const int  MAXN = 50005 ;
inline  int  read(){
    int s = 0 ; char g=getchar() ;while(g>'9'||g<'0')g=getchar() ;
    while(g>='0'&&g<='9')s=s*10+g-'0',g=getchar();return s ;
}
int head[ MAXN ] , to[ MAXN*2 ] , nex[ MAXN*2 ] ;
int dfn[ MAXN ] , low[ MAXN ] , scc[ MAXN ] , number[ MAXN ] , ans[ MAXN ] ;
int  N , M , kind , num , tot = 1 ;
bool used[ MAXN ] ;
stack<int>q ; 
void  add( int x , int  y  ){
    to[ ++tot ] = y , nex[ tot ] = head[ x ] , head[ x ] = tot ;
}
void  tarjan( int u ){
    dfn[ u ] = low[ u ] = ++num ; used[ u ] =true ; 
    q.push( u ) ;
    int  flag = 0 ;
    for( int i = head[ u ] ; i ; i = nex[ i ] ){
        if( !dfn[ to[ i ] ] ){
            tarjan( to[ i ] ) ;
            low[ u ] = min( low[ u ] , low[ to[ i ] ] ) ;
        }
        else if( used[ to[ i ] ] ) low[ u ] = min ( low[ u ] ,dfn[ to[ i ] ] ) ;
    }
    if( low[ u ] == dfn[ u ] ){
        kind++ ;
        int  now = -1 ;
        while( now != u ){
            now = q.top() ; q.pop() ;
            used[ now ] = false ; 
            number[ kind ]++ ; scc[ now ] = kind ;
        }
    }
}
int main(){
    N = read() , M = read() ;
    int  m1 , m2 ;
    for( int i = 1 ; i <= M ; ++i ){
        m1 = read() , m2 = read() ; if( m1 == m2 )continue ;
        add( m1 , m2 ) ;
    }
    for( int i = 1 ; i <= N ; ++i )
        if( !dfn[ i ] )tarjan( i ) ;
    int anss = 0 ;
    for( int u = 1 ; u <= N ; ++u )
        for( int i = head[ u ] ; i ; i = nex[ i ] ){
            if( scc[ u ] != scc[ to[ i ] ] )ans[ scc[ u ] ]++ ;
        }
    for( int i = 1 ; i <= kind ; ++i )
        if( !ans[ i ] )anss++ ;
    if( anss > 1 || anss == 0 ){cout<<0;return 0;}
    for( int i = 1 ; i <= kind ; ++i )
        if( !ans[ i ] ){ anss = number[ i ] ; break ; }
    cout<<anss ;
    return 0 ;
}

[USACO5.3]校园网Network of Schools

【题面】

一些学校连入一个电脑网络。那些学校已订立了协议:每个学校都会给其它的一些学校分发软件(称作“接受学校”)。注意即使 B 在 A 学校的分发列表中, A 也不一定在 B 学校的列表中。

你要写一个程序计算,根据协议,为了让网络中所有的学校都用上新软件,必须接受新软件副本的最少学校数目(子任务 A)。更进一步,我们想要确定通过给任意一个学校发送新软件,这个软件就会分发到网络中的所有学校。为了完成这个任务,我们可能必须扩展接收学校列表,使其加入新成员。计算最少需要增加几个扩展,使得不论我们给哪个学校发送新软件,它都会到达其余所有的学校(子任务 B)。一个扩展就是在一个学校的接收学校列表中引入一个新成员。

输入格式

输入文件的第一行包括一个整数 N:网络中的学校数目(2 <= N <= 100)。学校用前 N 个正整数标识。

接下来 N 行中每行都表示一个接收学校列表(分发列表)。第 i+1 行包括学校 i 的接收学校的标识符。每个列表用 0 结束。空列表只用一个 0 表示。

输出格式

你的程序应该在输出文件中输出两行。

第一行应该包括一个正整数:子任务 A 的解。

第二行应该包括子任务 B 的解。

【思路】

先考虑子任务A:

我们发现,缩点后,如果仍存在一个点(强连通分量)的入读为0,那么这个点(强连通分量)一定需要一个软件副本,而且在给所有上述点软件副本后,其他的点一定会从已有的边获得。

所以子任务A的答案就是缩点后入读为0的边。

在考虑子任务B:

我们要把原图加边后加成一个强连通图(不是极大强连通分量),我们考虑,使原图不满足强连通图的点肯定是缩点后 入读为0 或者 出度为0 的点。考虑:1.如果同时存在 一个入读为0的点 A 和 出度为0的点 B 这样的点对,那么最优策略一定是从 B 向 A 连边。
2.如果 A 点单独存在,从任意一点连接返祖边指向A即可 , B 点单独存在,我们只需要让其连接一条从B开始的返祖边即可。
不断执行上述过程,1操作优先,然后是2操作 , 每次操作都可以是原图转化为一个次级问题(需要连接更少的边)。 必定可以得到一个极大的强连通图。

特殊情况,若原图是一个强连通分量时,直接输出 0 。

【代码】

#include<bits/stdc++.h>
using namespace std; 
const int MAXN = 10005 ;
inline int read(){
    int s=0 ; char g=getchar() ; while( g>'9'||g<'0' )g=getchar() ;
    while( g>='0'&&g<='9')s=s*10+g-'0',g=getchar() ; return s ;
}
int  to[ MAXN ] , nex[ MAXN ] , head[ 105 ] , tot = 1 , num , kind ; 
int  N , M , dfn[ 105 ] , low[ 105 ] , scc[ 105 ] , number[ 105 ] , du[ MAXN ] , du2[ MAXN ]; 
bool vis[ MAXN ] ;
stack<int>q ;
void  add( int x , int y ){
    to[ ++tot ] = y , nex[ tot ] = head[ x ] , head[ x ] = tot ; 
}
void  tarjan( int u ){
    dfn[ u ] = low[ u ] = ++num ; vis[ u ] = true ;
    q.push( u ) ;
    for( int i = head[ u ] ; i ; i = nex[ i ] )
        if( !dfn[ to[ i ]  ] ){
            tarjan( to[ i ] ) ;
            low[ u ] = min( low[ u ] , low[ to[ i ] ] ) ;
        }
        else if( vis[ to[ i ] ] )low[ u ] = min( low[ u ] , dfn[ to [ i ] ] ) ;
    if( low[ u ] == dfn[ u ] ){
        kind++ ; int now = -1 ; 
        while( now != u ){
            now = q.top() ; q.pop() ;
            vis[ now ] = false ; 
            number[ kind ]++ , scc[ now ] = kind ; 
        }
    }
}
int main(){
    N =  read() ; int m1 ;
    for( int i = 1 ; i <= N ; ++i ){
        while(true){
            m1 = read() ;if( !m1 )break ; 
            add( i , m1 ) ;
        }
    }
    for( int i = 1 ; i <= N ; ++i )
        if( !dfn[ i ] )tarjan( i ) ;
    for( int u = 1 ; u <= N ; ++u )
        for( int i = head[ u ] ; i ; i = nex[ i ] )
            if( scc[ u ] != scc[ to[ i ] ] )du[ scc[ to[i] ] ]++ , du2[ scc[ u ] ]++ ;
    int ans = 0 , ans2 = 0 ; 
    for( int i = 1 ; i <= kind ; ++i ){
        if( !du[ i ] )ans++;
        if( !du2[ i ] )ans2++ ;
    }
    cout<<ans<<endl;
    if( kind == 1 )cout<<0 ; //注意特判 
    else cout<<max(ans,ans2) ;
    return 0 ;  
}

转载于:https://www.cnblogs.com/ssw02/p/11515086.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值