loj 1308(点双连通分量应用)
题目链接:http://acm.hust.edu.cn/vjudge/problem/viewProblem.action?id=27016
题意:求一个连通图去掉任意一个点以及与它相连的边,图中的所有蚂蚁可以通过某些点中建造的矿井逃到地面,求最少要在图中的几个点中建造矿建,这样建造矿井的方案的总数。
思路:首先求点双连通分量,标记割点,然后我们可以分析,若一个连通分量中有且仅有一个割点,那么除了这个割点之外,别的点都能造矿井,且只需在除了这个割点之外的某个点上造一座矿井就可以了(假设破话的是这个割点,那么这个连通分量中的蚂蚁可以通过有矿井的点逃到表面,但是如果矿井恰好造在割点上,那么一旦去掉了割点之后,蚂蚁就无法逃到地面上去了(这就是为什么不选割点的原因)。至于为什么别的点可以呢,假设破话的点恰好就是有矿井的这个点,那么这个连通分量中的蚂蚁可以通过割点跑到别的点双连通分量中逃生)。然后我们就只需要找有且只有一个割点的点双连通分量有多少个就行了,然后在每个连通分量中选择任何一个不是割点的点建造矿井就行了,至于有多少种方案,也就好求了,直接连通分量的点的个数(除去割点)相乘即可。然后我想说一下为什么对于那些点连通分量中割点大于等于2的就不用选呢,其实我们画一下图就知道了,因为破话这个连通分量的任何一个点以及与它相连的边,这个连通分量的中蚂蚁还是可以通过其中一个割点跑到另一个连通分量中去逃生。这样我们就得到了割点数目等于1和大于等于2的情况,那么对于图中没有割点的情况我们应该怎么选,这也好办,画个图就明白了,只需任选两个点建造矿井就可以了,当破话了某个有矿井的点之后,蚂蚁可以通过另一个有矿井的点逃生,那么总的方案数为n*(n-1)/2。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<stack> 6 #include<queue> 7 #include<vector> 8 using namespace std; 9 #define MAXN 22222 10 typedef long long ll; 11 typedef unsigned long long llu; 12 13 struct Edge{ 14 int v,next; 15 }edge[MAXN<<1]; 16 17 int n,m,NE; 18 int head[MAXN]; 19 20 void Insert(int u,int v) 21 { 22 edge[NE].v=v; 23 edge[NE].next=head[u]; 24 head[u]=NE++; 25 } 26 27 int cnt,bcc_count,cut_count; 28 int low[MAXN],dfn[MAXN]; 29 vector<vector<int> >blocks; 30 bool mark[MAXN]; 31 bool is_cutpoint[MAXN]; 32 stack<int>S; 33 34 void Tarjan(int root,int u,int father) 35 { 36 int rt_son=0; 37 low[u]=dfn[u]=++cnt; 38 mark[u]=true; 39 S.push(u); 40 for(int i=head[u];i!=-1;i=edge[i].next){ 41 int v=edge[i].v; 42 if(v==father)continue; 43 if(dfn[v]==0){ 44 Tarjan(root,v,u); 45 low[u]=min(low[u],low[v]); 46 if(u==root)rt_son++; 47 if(low[v]>=dfn[u]){ 48 int x; 49 do{ 50 x=S.top(); 51 S.pop(); 52 mark[x]=false; 53 blocks[bcc_count].push_back(x); 54 }while(v!=x); 55 blocks[bcc_count].push_back(u); 56 bcc_count++; 57 if(u!=root)is_cutpoint[u]=true; 58 } 59 }else if(mark[v]){ 60 low[u]=min(low[u],dfn[v]); 61 } 62 } 63 if(u==root&&rt_son>=2)is_cutpoint[u]=true; 64 } 65 66 int main() 67 { 68 int _case,u,v,t=1; 69 scanf("%d",&_case); 70 while(_case--){ 71 scanf("%d%d",&n,&m); 72 NE=0; 73 memset(head,-1,sizeof(head)); 74 blocks.clear(); 75 blocks.resize(n+2); 76 while(!S.empty())S.pop(); 77 while(m--){ 78 scanf("%d%d",&u,&v); 79 Insert(u,v); 80 Insert(v,u); 81 } 82 cnt=bcc_count=0; 83 memset(mark,false,sizeof(mark)); 84 memset(is_cutpoint,false,sizeof(is_cutpoint)); 85 memset(dfn,0,sizeof(dfn)); 86 Tarjan(0,0,-1); 87 ll ans=0; 88 llu ways=1; 89 int cut_count=0; 90 for(int i=0;i<n;i++)if(is_cutpoint[i])cut_count++; 91 if(cut_count==0){ 92 printf("Case %d: %d %d\n",t++,2,n*(n-1)/2); 93 continue; 94 } 95 for(int i=0;i<bcc_count;i++){ 96 cnt=0; 97 for(int j=0;j<blocks[i].size();j++){ 98 int v=blocks[i][j]; 99 if(is_cutpoint[v])cnt++; 100 } 101 if(cnt<2){ 102 ans++; 103 ways*=(llu)(blocks[i].size()-1); 104 } 105 } 106 printf("Case %d: %lld %llu\n",t++,ans,ways); 107 } 108 return 0; 109 110 }