点双连通分量模板–矿厂搭建
题目传送门:这是BZOJ2730的传送门
题目传送门:这是codevs1996的传送门
题目传送门:这是 洛谷 3225的传送门
抱怨几句
模板来自刘汝佳那本蓝书,me一开始在理解代码的时候,弄错了一个小的判断句,认为其没有卵用…..
然后这道题出现了,让me对那个判断句刮目相看。
啊啊啊啊啊啊啊调了几个小时!!!
·····························
题目分析
题意: 中文题,自己去看←_← (其实是不想打字……)
思路: 题目说,任意一个矿点坍塌之后,其余的矿场到安全通道至少有一条路。
对于一个无向图来说,只有在割点被切掉时,其余点连通性才会改变。
分类讨论:
对于每一堆 “仅含一个割点的点双连通分量”,如果位于割点的矿场坍塌,被分割出来的部分矿场就需要建一个安全通道。
那些“含有两个割点的双连通分量”,就算有一个位于割点的矿场坍塌,这个分量里所有的矿场可以通过另一个割点与外界连通,因此不需要建立安全通道。
如果整个图全部都是双连通分量,即不含割点,那么我们需要建造两个安全通道,一个塌了还可以走另一个
建造方法总数计算也比较简单,如果整个图无割点,那么方案总数乘上C(n,2),不然乘上(n-1) 。其中n是每个连通分量的点个数。
坑点
题目只告诉边数,并没有告诉点数,me们需要在输入的时候对点编号取max,放心编号一定是从1开始且连续。为什么?因为me已经AC了hhhhhhh。
还有就是,可能有多个图。
ps:有8个点只有一个图,数据略水。
代码:
BZOJ: M:860Kb T:0ms L:2786 B
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
using namespace std ;
int N,maxn,ans,cntn;
long long esc_cnt ;
struct ori_path {
int fr , pre , to ;
}ori_p[1005] ;
int ori_head[1005] , ori_tp = 1 ;
void In( int t1 , int t2 ){
ori_p[ ++ori_tp ].pre = ori_head[t1];
ori_p[ ori_tp ].to = t2 ;
ori_head[t1] = ori_tp ;
ori_p[ ori_tp ].fr = t1 ;
}
vector< int > bcc[1005] ;
int bccno[1005] , topp , b_time , dfn[1005] , bcc_cnt , sta[1005] ;
bool is_cut[1005];
int bcc_tarjan( int u , int f ){
cntn++;
int lowu = dfn[u] = ++b_time , child = 0 ;
for( int i = ori_head[u] ; i ; i = ori_p[i].pre ) {
int v = ori_p[i].to ;
if( !dfn[v] ){
sta[ ++topp ] = i ;
child++ ;
int lowv = bcc_tarjan( v , u );
lowu = min ( lowu , lowv );
if( lowv >= dfn[u] ){
is_cut[u] = true ;
bcc_cnt++ ; bcc[ bcc_cnt ].clear() ;
while(1){
ori_path t = ori_p[ sta[ topp-- ] ] ;
if( bccno[ t.fr ] != bcc_cnt ){
bcc[ bcc_cnt ].push_back( t.fr ) ;bccno[ t.fr ] = bcc_cnt;
}
if( bccno[ t.to ] != bcc_cnt ){
bcc[ bcc_cnt ].push_back( t.to ) ;bccno[ t.to ] = bcc_cnt;
}
if( t.fr == u && t.to == v ) break;
}
}
} else if ( dfn[v] < dfn[u] && v != f ){
sta[ ++topp ] = i ;
lowu = min( lowu , dfn[v] ) ;
}
}
if ( f < 0 && child == 1 ) is_cut[u] = false ;
return lowu ;
}
void solve(){
for( int i = 1 ; i <= bcc_cnt ; ++ i ){
int cnt_cut = 0 , siz = bcc[i].size() ;
for(int j = 0 ; j < siz ; ++ j ){
if( is_cut[ bcc[i][j] ] ) cnt_cut++;
if( cnt_cut > 1 ) break;
}
if( cnt_cut == 1 ){
esc_cnt *=(long long )(siz - 1 ) ;
ans += 1 ;
} else if ( cnt_cut == 0 ){
ans += 2 ;
esc_cnt *= (long long )(siz - 1) * siz / 2 ;
}
}
}
void clear(){
ori_tp = 1 ; ans = 0 ; esc_cnt = 1LL ; maxn = 0 ; b_time = 0 ;
memset( dfn , 0 , sizeof(dfn) ) ;
memset( bccno , 0 , sizeof(bccno) ) ;
memset( is_cut , false , sizeof(is_cut) ) ;
memset( ori_head , 0 , sizeof(ori_head) ) ;
}
int main(){
int t1 , t2 , cas = 0 ;
while(scanf("%d",&N)&&N!=0){
cas++ ;
clear();
for( int i = 1 ; i <= N ; ++i ) {
scanf( "%d%d" , &t1 , &t2 ) ;
maxn = max( maxn , t1 );
maxn = max( maxn , t2 );
if(t1 != t2){
In( t1 , t2 ) ; In( t2 , t1 ) ;
} else ans ++ ;
}
for(int i = 1 ; i <= maxn ; ++ i )
if( !dfn[i] ) {
bcc_cnt = 0 ; cntn = 0 ; topp = 0;
bcc_tarjan( i , -1 );solve();
}
printf("Case %d: %d %lld\n",cas,ans,esc_cnt);
}
}
附赠一组小数据
in:
6
1 2
2 3
3 4
4 5
5 6
6 2
out:
Case 1: 2 4