这道题虽然看着像是道模板题,实际上有很多要注意的特殊情况。
我们对坍塌的点进行分类讨论。如果坍塌的点不是割点,显然不会影响图的连通性,也就不会影响其他人的逃生。考虑最简单的情况:一张图没有割点。我们是不是只需要个出口就够了?并非如此,因为可能坍塌的点就是出口...因此,我们需要再设
个出口作为备用,个数为
,方案数为
(
为点数)。如果有割点怎么办?先来看一个最简单的例子:
—
—
。如果坍塌的点为
,我们需要在点
或
设置出口;如果坍塌的点为
(
为割点),我们需要在点
和
设置出口;如果坍塌的点为
,我们需要在点
或
设置出口。因此,最优方案为在点
和
设置出口。显然,坍塌的点如果是割点,我们就需要更多的出口(因为图连通性被破坏),同时,我们把任何一个出口设在割点也都是不优的。为什么呢?如果坍塌的点不是割点,把出口设在哪个点都无所谓;如果坍塌的点是割点(同时也是我们设的出口),我们就需要在删去割点后形成的每个连通子图中再设一个出口。反过来想想,在删去割点后形成的每个连通子图中再设一个出口,不就是最优方案了吗?至于方案数,就是所有连通子图大小的乘积(简单的乘法原理)。然而,我们还有一种特殊情况没考虑到:如果一个连通子图与两个及以上的割点相连,这个连通子图内就不需要设置出口!为什么呢?如果一个连通子图与两个及以上的割点相连,那么无论删去哪个割点,这个连通子图内的人都能经过其它割点跑到别的连通子图的出口中,自然不用多设出口。完整代码如下。
#include<cstdio>
#include<cstring>
#define maxn 50010
#define r register
#define cmax(_x,_y) (_x>_y?_x:_y)
#define cmin(_x,_y) (_x<_y?_x:_y)
using namespace std;
int t,n,m,num,a,b,ans1,first[maxn],next[maxn<<1],to[maxn<<1];
int now,cnt,dfn[maxn],low[maxn],size[maxn],deg[maxn];
//now表示dfs序,cnt表示删去所有割点后的连通子图个数,deg表示一个与连通子图相连的割点个数
long long ans2;
bool cut[maxn],vis[maxn],cal[maxn];
//cal[i]表示割点i是否被一个连通子图计算过(防止同一个割点被同一个连通子图计算多次)
int read()
{
r char ch=getchar();r int in=0;
while(ch>'9'||ch<'0') ch=getchar();
while(ch<='9'&&ch>='0') in=(in<<1)+(in<<3)+ch-'0',ch=getchar();
return in;
}
void tarjan(r int x,r int flag)//tarjan算法求割点,flag用于特判1号点是不是割点
{
dfn[x]=low[x]=++now;
for(r int i=first[x];i;i=next[i])
if(!dfn[to[i]])
{
tarjan(to[i],0);
low[x]=cmin(low[x],low[to[i]]);
if(low[to[i]]<dfn[x]) continue;
if(++flag>1||x!=1) cut[x]=1;//如果1号点有两个及以上的子结点,它就是割点
}
else low[x]=cmin(low[x],dfn[to[i]]);
}
void dfs(r int x)
{
vis[x]=1,size[cnt]++;
for(r int i=first[x];i;i=next[i])
{
if(!vis[to[i]]&&!cut[to[i]]) dfs(to[i]);
else if(cut[to[i]]&&!cal[to[i]]) deg[cnt]++,cal[to[i]]=1;
}
}
int main()
{
while(m=read(),m)
{
n=num=now=cnt=0;
memset(to,0,sizeof(to));
memset(next,0,sizeof(next));
memset(first,0,sizeof(first));
for(r int i=1;i<=m;i++)
{
a=read(),b=read();
n=cmax(n,cmax(a,b));
to[++num]=b,next[num]=first[a],first[a]=num;
to[++num]=a,next[num]=first[b],first[b]=num;
}
memset(dfn,0,sizeof(dfn));//初始化真麻烦
memset(low,0,sizeof(low));
memset(cut,0,sizeof(cut));
memset(vis,0,sizeof(vis));
memset(deg,0,sizeof(deg));
memset(size,0,sizeof(size));
tarjan(1,0),ans1=0,ans2=1;
for(r int i=1;i<=n;i++)
if(!vis[i]&&!cut[i])
{
memset(cal,0,sizeof(cal)),cnt++,dfs(i);//每次dfs都要初始化cal数组
if(deg[cnt]<2) ans1++,ans2=1ll*ans2*size[cnt];
//只有相连的割点数不超过2,才计入答案
}
if(cnt==1) printf("Case %d: 2 %lld\n",++t,1ll*n*(n-1)/2);
else printf("Case %d: %d %lld\n",++t,ans1,ans2);
//如果不存在割点(整张图是一个点双连通分量)或删去所有割点后只剩下1个连通子图,需要特判
}
return 0;
}