一开始的思路是
tarjan求出点双,然后缩点,答案是叶子结点个数,方案数是叶子结点size相乘,也就是每个叶子上建一个出口
但注意只有一个节点的时候方案数要乘以size*(size-1)/2,就是这个节点要建两个防止一个被封掉
但其实不用缩点,因为点双缩点后形成的森林都是由割点连接的,所以叶子结点只可能连接一个或零个割点
通过这个来分类求解,注意tarjan求点双的时候每个点双的size是算上割点的,所以在连接一个割点的情况下要-1
于是一个看起来很复杂的问题就这样被一个模板+一点分类就解决了
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define maxn 1005
#define LL long long
using namespace std;
int n,m,cnt,head[maxn],cas,root;
LL ans1,ans2;
int dfn[maxn],low[maxn],num,dc,stk[maxn],top;
bool cut[maxn];
vector<int> dcc[maxn];
inline int rd(){
int x=0,f=1;char c=' ';
while(c<'0' || c>'9') {if(c=='-')f=-1;c=getchar();}
while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
return x*f;
}
struct EDGE{
int to,nxt;
}edge[maxn];
inline void add(int x,int y){
edge[++cnt].to=y; edge[cnt].nxt=head[x]; head[x]=cnt;
}
inline void init(){
n=cnt=num=top=dc=0; ans1=0; ans2=1;
memset(dfn,0,sizeof dfn); memset(low,0,sizeof low);
memset(cut,0,sizeof cut); memset(head,0,sizeof head);
memset(dcc,0,sizeof dcc);
}
inline void tarjan(int u){
dfn[u]=low[u]=++num; stk[++top]=u;
if(u==root && head[u]==0){
dcc[++dc].push_back(u); return;
}
int flag=0;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].to;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
if(low[v]>=dfn[u]){
flag++;
if(u!=root || flag>1) cut[u]=true;
++dc;
int z;
do{
z=stk[top--];
dcc[dc].push_back(z);
if(z==0) while(1);
}while(z!=v);
dcc[dc].push_back(u);
}
}
else low[u]=min(low[u],dfn[v]);
}
}
int main(){
while(1){
m=rd(); if(!m) break;
cas++; init();
for(int i=1;i<=m;i++){
int x=rd(),y=rd();
add(x,y); add(y,x);
n=max(n,max(x,y));
}
for(int i=1;i<=n;i++){
if(!dfn[i]){
root=i;
tarjan(i);
}
}
for(int i=1;i<=dc;i++){
int tot=0,tmp=dcc[i].size();
for(int j=0;j<tmp;j++){
int x=dcc[i][j];
if(cut[x]) tot++;
}
if(tot==0) {
if(tmp==1) ans1++;
else ans1+=2,ans2*=(tmp*(tmp-1)/2);
}
if(tot==1) ans1++,ans2*=(tmp-1);
}
printf("Case %d: %lld %lld\n",cas,ans1,ans2);
}
return 0;
}