396. 矿场搭建
题目链接
396. 矿场搭建
煤矿工地可以看成是由隧道连接挖煤点组成的无向图。
为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。
于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。
请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。
输入格式
输入文件有若干组数据,每组数据的第一行是一个正整数 \(N\),表示工地的隧道数。
接下来的 \(N\) 行每行是用空格隔开的两个整数 \(S\) 和 \(T\),表示挖煤点 \(S\) 与挖煤点 \(T\) 由隧道直接连接。
注意,每组数据的挖煤点的编号为 \(1 \sim Max\),其中 \(Max\) 表示由隧道连接的挖煤点中,编号最大的挖煤点的编号,可能存在没有被隧道连接的挖煤点。
输入数据以 \(0\) 结尾。
输出格式
每组数据输出结果占一行。
其中第 \(i\) 行以 Case i:
开始(注意大小写,Case
与 i
之间有空格,i
与 :
之间无空格,:
之后有空格)。
其后是用空格隔开的两个正整数,第一个正整数表示对于第 \(i\) 组输入数据至少需要设置几个救援出口,第二个正整数表示对于第 \(i\) 组输入数据不同最少救援出口的设置方案总数。
输入数据保证答案小于 \(2^{64}\),输出格式参照以下输入输出样例。
数据范围
\(1 \le N \le 500\),
\(1 \le Max \le 1000\)
输入样例:
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;
}