BZOJ2730 HNOI2012 矿井搭建 连通性
题意:给定一无向图,选择最少的点,使得删除任意一个点后,其他的点都与所选择的点中任意一个联通
题解:
由于割点上肯定不能放井,所以开始时删除所有的割点。然后找出所有的双连通分量,如果双连通分量上有两个及以上割点,不用放井;如果只有一个割点,一定要放一个井,然后乘法原理统计答案即可。
至于怎么求双连通分量,由于一个割点一定是在两个双连通分量交界的地方,因此DFS每个未访问节点,仅当一个点不为割点时扩展该节点。
#include <map> #include <cstdio> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> using namespace std; #define ll long long const int MAXN=500+2; struct HASH{ int u; HASH *next; HASH(){} HASH(int _u,HASH *_next):u(_u),next(_next){} }*table[MAXN],mem[2*MAXN]; int N,M,T,f,cnt,ans,t[MAXN],bcc[MAXN]; int dfn[MAXN],low[MAXN],depth; ll s[MAXN],ans_sum; bool flag[MAXN],cut[MAXN]; map<int,int> m; void Insert(int u,int v){ table[u]=&(mem[cnt++]=HASH(v,table[u])); table[v]=&(mem[cnt++]=HASH(u,table[v])); } void Tarjan(int f,int x){ dfn[x]=low[x]=++depth; int c=0; for(HASH *p=table[x];p;p=p->next) if(!dfn[p->u]){ Tarjan(x,p->u),low[x]=min(low[x],low[p->u]),c++; if(low[p->u]>=dfn[x]) cut[x]=1; } else if(p->u!=f && dfn[p->u]<dfn[x]) low[x]=min(low[x],dfn[p->u]); if(!f && c==1) cut[x]=0; } void DFS(int x){ flag[x]=1,s[cnt]++; for(HASH *p=table[x];p;p=p->next){ if(flag[p->u]) continue; if(!cut[p->u]) DFS(p->u); else if(bcc[p->u]!=cnt) bcc[p->u]=cnt,t[cnt]++; } } int main(){ while(scanf("%d",&M)!=EOF && M){ N=cnt=ans=depth=0,ans_sum=1,T++; m.clear(); memset(t,0,sizeof(t)); memset(s,0,sizeof(s)); memset(low,0,sizeof(low)); memset(dfn,0,sizeof(dfn)); memset(cut,0,sizeof(cut)); memset(bcc,0,sizeof(bcc)); memset(flag,0,sizeof(flag)); memset(table,0,sizeof(table)); for(int i=1,u,v;i<=M;i++){ scanf("%d %d",&u,&v); if(!m[u]) m[u]=++N; if(!m[v]) m[v]=++N; Insert(m[u],m[v]); } for(int i=1;i<=N;i++) if(!dfn[i]) Tarjan(0,i); cnt=0; for(int i=1;i<=N;i++) if(!cut[i] && !flag[i]) ++cnt,DFS(i); if(cnt==1) printf("Case %d: 2 %lld\n",T,N*(N-1)/2); else{ for(int i=1;i<=cnt;i++) if(t[i]==1) ans++,ans_sum*=s[i]; printf("Case %d: %d %lld\n",T,ans,ans_sum); } } return 0; }