强连通分量大礼包
Posted ssw02
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了强连通分量大礼包相关的知识,希望对你有一定的参考价值。
强连通分量大礼包
主要用基础题将一些有向图的强连通分量的题
难度递增,不过都水
[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 ;
以上是关于强连通分量大礼包的主要内容,如果未能解决你的问题,请参考以下文章