题目传送门:https://www.luogu.org/problemnew/show/P3225
题意:
有m条边,现在在数量尽量少的点上加上安全出口,使得剩下的无论哪一个点坍塌,其它点都能到达安全(即不坍塌)的点。
思路:
由“使得剩下的无论哪一个点坍塌”可知是要求割点。
我们跑完割点后再跑一遍dfs(在我的代码中是go),求出每一块的割点的数目以及总数,最后用组合公式搞搞就可以了。
对于当前块来说:
1.没有割点,那么就加上两个点作为安全出口;
2.只有一个割点,就加上一个点作为安全出口;
3.大于一个割点,任意一个割点坍塌,都可以从另一个割点出去,就不用加。
什么是块呢,不懂是吧(其实是我语文不好,不好表述),如下图:
3是割点,则(1,3),(2,3,4,5)分别为一个块。
通俗来说,dfs时到达割点就不再往下进行了;其它点往下继续dfs,直到从当前这个点无法再连出边时所有被访问过的点所组成的集合为之一个块(只是在这道题中我自己定义的)。
再看回图,
从1开始搜,
1.搜到3,为割点,不拓展;
结束。
从2开始搜,
1.搜到3,为割点,不拓展;
2.搜到5,继续拓展;
从5开始搜,
1.搜到4,继续拓展;
从4开始搜,
1.搜到3,为割点,不拓展;
结束。
注意:以上省略了反向边的过程(因为一个点只能被遍历一次(一个非割点的点必然只出现在一个快中),所以就省略了)。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define _ 1010
#define LL long long
using namespace std;
struct node{int x,y,next;} a[_];
int n,m,len,id,now;
LL sum_zong,sum_gedian,ans1,ans2;
int last[_],low[_],dfn[_],p[_];
bool bz[_];
void init()
{
n=len=id=now=ans1=0;
ans2=1;
memset(last,0,sizeof(last));
memset(low,0,sizeof(low));
memset(dfn,0,sizeof(dfn));
memset(p,0,sizeof(p));
memset(bz,false,sizeof(bz));
}
void ins(int x,int y)
{
a[++len].x=x;a[len].y=y;a[len].next=last[x];last[x]=len;
}
void dfs(int x,int root)
{
int tot=0;
low[x]=dfn[x]=++id;
for(int i=last[x];i;i=a[i].next)
{
int y=a[i].y;
if(!dfn[y])
{
dfs(y,root);
low[x]=min(low[x],low[y]);
if(low[y]>=dfn[x]&&x!=root) bz[x]=true;
if(x==root) tot++;
}
low[x]=min(low[x],dfn[y]);
}
if(x==root&&tot>=2) bz[root]=true;
}
void go(int x)
{
p[x]=now;
if(bz[x]) return;
sum_zong++;
for(int i=last[x];i;i=a[i].next)
{
int y=a[i].y;
if(bz[y]&&p[y]!=now) sum_gedian++,p[y]=now;
if(!p[y]) go(y);
}
}
int main()
{
int T=0,x,y;
while(++T)
{
scanf("%d",&m);
if(!m) break;
init();
for(int i=1;i<=m;i++)
{
scanf("%d %d",&x,&y);
n=max(n,max(x,y));
ins(x,y);
ins(y,x);
}
for(int i=1;i<=n;i++)
if(!dfn[i]) dfs(i,i);
for(int i=1;i<=n;i++)
if(!p[i]&&!bz[i])
{
sum_zong=sum_gedian=0;
now++;
go(i);
if(sum_gedian==1) ans1++,ans2*=sum_zong;
if(!sum_gedian) ans1+=2,ans2*=sum_zong*(sum_zong-1)/2;
}
printf("Case %d: %lld %lld\n",T,ans1,ans2);
}
}