给无向图 问最少在几个点设置出口 可以在某个出口删除的时候 其余点可以与某个出口联通 求出点连通分量
当发现dfn[x]<= low[j] 说明y搜不到x上面 说明x是子树
if(dfs(x)<=low(y)){
cnt++;
if(x不是根节点|cnt>1) 说明x是割点
将栈中元素弹出直到弹出y为止 而且x也是在双连通分量里面
}
割点属于两个双连通分量
1.出口数量大于等于2
2.不同连通块之间相互独立(最终方案数 = 各连通块方案数乘积)
分别看每个连通块
1.无割点 至少连个出口 剩下联通 删除一点可以走到出口
2.有割点 缩点
1.每个割点单独作为一个点
2.从每个v-dcc向其包含的每个割点都连边
总结:
1 无割点 放2个出口 方案数 = C[cnt][2] = cnt(cnt-1)/2
2 有割点 V-DCC==1 放1个出口 方案数 *= C[cnt-1][1] = cnt-1 (不包含割点)
3 有割点 V-DCC>=2 放0个出口 方案数 *= 1
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
typedef unsigned long long LL;
using namespace std;
const int N = 1010,M=1010;
int n,m;
int h[N], e[M], ne[M], idx;
int dfn[N], low[N], timestamp; // 时间戳
int stk[N], top;
int dcc_cnt;//记录每个点是否为割点
vector<int> dcc[N]; // 每个分量有哪些点
bool cut[N]; // 是否为割点
int root;
void add(int a, int b) // 添加一条边a->b
{
e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;
}
void tarjan(int u){
dfn[u] =low[u] =++timestamp;
stk[++top]=u;
if(u==root&&h[u]==-1){ //u是根节点 而且没有领边
dcc_cnt++;//本来就是一个点 点双连通数+1
dcc[dcc_cnt].push_back(u);//存点
return ;
}
//2 u不是孤立点
int cnt=0;
for (int i = h[u]; ~i ; i = ne[i] ){//从前往后遍历领边
int j=e[i];
if(!dfn[j]){//当前点没有被遍历过
tarjan(j);//搜索
low[u]=min(low[u],low[j]);//更新 看j是不是能连到比u更高的地方
if(dfn[u] <= low[j]){//判断是否j是不是不能走到u之上
cnt++;//说明多了个分支
if(u!=root || cnt>1) cut[u]=true;//如果不是根节点 就是割点 否则如果有两个分支 就是割点 这里已经不讨论叶子节点的情况
++ dcc_cnt;//点连通数+1
int y;
do{
y=stk[top--];
dcc[dcc_cnt].push_back(y);
}while(y!=j);//弹到y为 因为j不出栈
dcc[dcc_cnt].push_back(u);//记得u也是属于这个点联通量的
}
}
else low[u]=min(low[u],dfn[j]);
}
}
int main()
{
int t =1;
while (cin>>m ,m){
for (int i = 1; i <= dcc_cnt; i ++ ) dcc[i].clear();
idx=n=timestamp=top=dcc_cnt=0;
memset(h, -1, sizeof h);
memset(dfn, 0, sizeof dfn);
memset(cut, 0, sizeof cut);
while (m -- ){
int a,b;
cin >> a >> b;
n=max(n,a),n=max(n,b);//更新点编号
add(a, b);
add(b, a);
}
for (root = 1; root <= n; root ++ ){//从前往后遍历所有店
if(!dfn[root]){
tarjan(root);
}
}
int res=0;
LL num=1;//方案数
for (int i = 1; i <= dcc_cnt; i ++ ){
int cnt=0;
for (int j = 0; j < dcc[i].size(); j ++ ){//看点联通分量的所有点
if(cut[dcc[i][j]]){//如果是割点的话
cnt++;
}
}
//如果没有割点 需要加两个点
if(cnt==0){
if(dcc[i].size()>1)
res+=2,num *= dcc[i].size()*(dcc[i].size()-1)/2;
else
res++;
}
//如果只有一个割点的话
else if(cnt==1) res++,num *= dcc[i].size()-1;
}
printf("Case %d: %d %llu\n",t++ ,res,num);
}
return 0;
}