加载中...

给无向图 问最少在几个点设置出口 可以在某个出口删除的时候 其余点可以与某个出口联通 求出点连通分量

当发现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;
}
posted @ 2022-08-31 19:32  liang302  阅读(23)  评论(0编辑  收藏  举报