396. 矿场搭建

题目链接

396. 矿场搭建

煤矿工地可以看成是由隧道连接挖煤点组成的无向图。

为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。

于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。

请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。

输入格式

输入文件有若干组数据,每组数据的第一行是一个正整数 N,表示工地的隧道数。

接下来的 N 行每行是用空格隔开的两个整数 ST,表示挖煤点 S 与挖煤点 T 由隧道直接连接。

注意,每组数据的挖煤点的编号为 1Max,其中 Max 表示由隧道连接的挖煤点中,编号最大的挖煤点的编号,可能存在没有被隧道连接的挖煤点。

输入数据以 0 结尾。

输出格式

每组数据输出结果占一行。

其中第 i 行以 Case i: 开始(注意大小写,Casei 之间有空格,i: 之间无空格,: 之后有空格)。

其后是用空格隔开的两个正整数,第一个正整数表示对于第 i 组输入数据至少需要设置几个救援出口,第二个正整数表示对于第 i 组输入数据不同最少救援出口的设置方案总数。

输入数据保证答案小于 264,输出格式参照以下输入输出样例。

数据范围

1N500
1Max1000

输入样例:

9 1 3 4 1 3 5 1 2 2 6 1 5 6 3 1 6 3 2 6 1 2 1 3 2 4 2 5 3 6 3 7 0

输出样例:

Case 1: 2 4 Case 2: 4 1

解题思路

缩点,点双连通分量

针对割点的缩点,是指将所有割点先独立起来,然后将所有的点双连通分量(其包含若干个割点)向对应的割点建边

本题要求在删除某个点其余所有点都可以到达出口点,求最少的出口点的数量和方案数,不妨先按割点缩点,这样会形成一棵树,可以知道,度数为 1 的节点上必须要设置一个出口点,否则因为如果其父亲节点或根节点的唯一一个儿子节点被删除时该节点对应原图的所有节点就到达不了其他出口点,而对于那些度数大于 1 的节点来说无论删除哪一个节点来说其都可以达到其他的某个出口点,而对于那些,另外如果树中只有一个节点,可能本就是孤立点,显然要在其上面设置一个出口点,否则说明没有割点,其必须要设置两个出口点,因为如果被删除的点正好是出口点的话,其他点也可以到达另外一个出口点

  • 时间复杂度:O(n+m)

代码

// Problem: 矿场搭建 // Contest: AcWing // URL: https://www.acwing.com/problem/content/398/ // Memory Limit: 64 MB // Time Limit: 1000 ms // // Powered by CP Editor (https://cpeditor.org) // %%%Skyqwq #include <bits/stdc++.h> //#define int long long #define help {cin.tie(NULL); cout.tie(NULL);} #define pb push_back #define fi first #define se second #define mkp make_pair using namespace std; typedef long long LL; typedef pair<int, int> PII; typedef pair<LL, LL> PLL; template <typename T> bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; } template <typename T> bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; } template <typename T> void inline read(T &x) { int f = 1; x = 0; char s = getchar(); while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); } while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar(); x *= f; } const int N=1005; int n,m; int h[N],ne[N],e[N],idx; int timestamp,dfn[N],low[N],dcc_cnt,stk[N],top; vector<int> dcc[N]; bool cut[N]; int root; void add(int a,int b) { e[idx]=b,ne[idx]=h[a],h[a]=idx++; } void tarjan(int x) { dfn[x]=low[x]=++timestamp; stk[++top]=x; if(h[x]==-1&&x==root) { dcc[++dcc_cnt].pb(x); return ; } int cnt=0; for(int i=h[x];~i;i=ne[i]) { int j=e[i]; if(!dfn[j]) { tarjan(j); low[x]=min(low[x],low[j]); if(dfn[x]<=low[j]) { cnt++; if(root!=x||cnt>1)cut[x]=true; dcc_cnt++; int y; do { y=stk[top--]; dcc[dcc_cnt].pb(y); }while(y!=j); dcc[dcc_cnt].pb(x); } } else low[x]=min(low[x],dfn[j]); } } int main() { int T=1; while(cin>>m,m) { for(int i=1;i<=dcc_cnt;i++)dcc[i].clear(); timestamp=dcc_cnt=top=n=idx=0; memset(h,-1,sizeof h); memset(dfn,0,sizeof dfn); memset(cut,0,sizeof cut); for(int i=1;i<=m;i++) { int x,y; cin>>x>>y; n=max(n,x),n=max(n,y); add(x,y),add(y,x); } for(root=1;root<=n;root++) if(!dfn[root])tarjan(root); int res1=0; unsigned long long res2=1; for(int i=1;i<=dcc_cnt;i++) { int cnt=0; for(int j:dcc[i])cnt+=cut[j]; if(cnt==0) { if(dcc[i].size()>1) res1+=2,res2*=dcc[i].size()*(dcc[i].size()-1)/2; else res1++; } else if(cnt==1)res1++,res2*=dcc[i].size()-1; } printf("Case %d: %d %llu\n",T++,res1,res2); } return 0; }

__EOF__

本文作者acwing_zyy
本文链接https://www.cnblogs.com/zyyun/p/16912133.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   zyy2001  阅读(21)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示