https://www.luogu.org/problem/P3225
题目描述
煤矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。
请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。
输入格式
输入文件有若干组数据,每组数据的第一行是一个正整数 N(N<=500),表示工地的隧道数,接下来的 N 行每行是用空格隔开的两个整数 S 和 T,表示挖 S 与挖煤点 T 由隧道直接连接。输入数据以 0 结尾。
输出格式
输入文件中有多少组数据,输出文件 output.txt 中就有多少行。每行对应一组输入数据的 结果。其中第 i 行以 Case i: 开始(注意大小写,Case 与 i 之间有空格,i 与:之间无空格,: 之后有空格),其后是用空格隔开的两个正整数,第一个正整数表示对于第 i 组输入数据至少需 要设置几个救援出口,第二个正整数表示对于第 i 组输入数据不同最少救援出口的设置方案总 数。输入数据保证答案小于 2^64。输出格式参照以下输入输出样例。
思路:这题挺难的。 t a r j a n tarjan tarjan求出所有点双连通分量和割点,然后对每个点双连通分量内的割点个数进行讨论:(1)没有割点,那么显然需要建立 2 2 2个出口,设该分量内有 n n n个点,则方案数为 n ∗ ( n − 1 ) / 2 n*(n-1)/2 n∗(n−1)/2;(2)仅有 1 1 1个割点,因为该割点可以通向别的点双连通分量,所以仅需要建立 1 1 1个出口,选择非割点的任意一个点即可,方案数为 n − 1 n-1 n−1;(3)割点数量 > = 2 >=2 >=2,因为割点通向别的点双连通分量,因此任意一个割点爆掉,工人都可以通过另外一个割点到达别的区域,所以不需要建立出口。出口总数可累加计算,方案总数通过乘法原理计算即可。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=505;
struct edge
{
int to,nxt;
}Edge[maxn<<1];
vector<int> dcc[maxn];//点双连通分量
bool cut[maxn];
int head[maxn],dfn[maxn],low[maxn],Stack[maxn];
int n,m,tot,num,root,top,cnt;
ll ans1,ans2;
inline void addedge(int x,int y)
{
Edge[++tot].to=y,Edge[tot].nxt=head[x],head[x]=tot;
}
void tarjan(int x)
{
int y,flag=0;
dfn[x]=low[x]=++num;
Stack[++top]=x;
if(x==root&&head[x]==0)//孤立点
{
dcc[++cnt].push_back(x);
return ;
}
for(int i=head[x];i;i=Edge[i].nxt)
{
y=Edge[i].to;
if(!dfn[y])//未访问过的节点
{
tarjan(y);
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x])
{
++flag;
if(x!=root||flag>=2)
cut[x]=1;
cnt++;
int z;
do
{
z=Stack[top--];
dcc[cnt].push_back(z);
}while(z!=y);
dcc[cnt].push_back(x);
}
}
else
low[x]=min(low[x],dfn[y]);
}
}
int main()
{
int times=0;
while(~scanf("%d",&m)&&m)
{
n=0;
memset(head,0,sizeof(head));
memset(dfn,0,sizeof(dfn));
memset(low,0,sizeof(low));
memset(cut,0,sizeof(cut));
num=tot=cnt=0;
int u,v;
for(int i=0;i<m;i++)
{
scanf("%d%d",&u,&v);
n=max(n,max(u,v));
addedge(u,v),addedge(v,u);
}
for(int i=1;i<=n;i++)
if(!dfn[i])
root=i,top=0,tarjan(i);
ans1=0,ans2=1;
for(int i=1;i<=cnt;i++)
{
int ct=0,len=dcc[i].size();
for(int j=0;j<len;j++)
if(cut[dcc[i][j]])
++ct;
if(ct==0)
ans1+=2,ans2*=len*(len-1)/2;
else if(ct==1)
ans1+=1,ans2*=len-1;
dcc[i].clear();
}
printf("Case %d: %lld %lld\n",++times,ans1,ans2);
}
return 0;
}