D163 vDCC 缩点 Tarjan 算法 P3225 [HNOI2012] 矿场搭建

D163 vDCC 缩点 Tarjan 算法 P3225 [HNOI2012] 矿场搭建_哔哩哔哩_bilibili

 

P3225 [HNOI2012] 矿场搭建 - 洛谷

地下无向图(不一定连通),在某些点上设置达到地面的出口,问最少设置几个出口,使得任意一点坍塌,其余点都可以走到出口

思路

参考:D16【模板】割点 Tarjan 算法 - 董晓 - 博客园  D19【模板】vDCC 缩点 Tarjan 算法 - 董晓 - 博客园

无向图按有无割点划分为两类:

1. 有割点类,图中有 2 和 5 两个割点,用 Tarjan 划分出 4 个 点双联通分量(vDCC)

 vDCC 内有一个割点时,在其它点上任选一个点设置出口,方案数 $*=(siz-1)$,$siz$ 是 vDCC 内的点数

 vDCC 内有两个(或更多)割点时,设置 0 个出口,方案数 $*=1$

 所以共设置 3 个出口,方案数 $=1*2*3*1=6$

    

2. 无割点类,下图用 Tarjan 划分出 3 个 点双联通分量(vDCC),均无割点

 左面 4 点中任选 2 点设置出口,方案数 $*=C_4^2$

 右面每个孤立点设置一个出口,方案数 $*=1$

 所以共设置 4 个出口,方案数 $=C_4^2*1*1=6$

    

 

// vDCC缩点 Tarjan算法 O(n+m)
#include<bits/stdc++.h>
using namespace std;

typedef unsigned long long ULL;
const int N=1010,M=1010;
vector<int> e[N];
int n,m;
int dfn[N],low[N],stk[N],top,cnt,root,cut[N];
vector<int> dcc[N];

void tarjan(int x){
  if(x==root&&e[x].size()==0){ //是孤立点
    dcc[++cnt].push_back(x);
    return;
  }
  
  dfn[x]=low[x]=++dfn[0]; stk[++top]=x;
  int son=0;
  for(int y:e[x]){
    if(!dfn[y]){
      tarjan(y);
      low[x]=min(low[x],low[y]);
      
      if(low[y]>=dfn[x]){
        ++son; //分支数
        if(x!=root||son>1) cut[x]=true; //x是割点
        ++cnt; //缩点编号
        while(1){
          int z=stk[top--];
          dcc[cnt].push_back(z);
          if(z==y)break; //让x留在栈中
        }
        dcc[cnt].push_back(x); //记录vDCC
      }
    }
    else low[x]=min(low[x],dfn[y]);
  }
}
int main(){
  int T=1;
  while(cin>>m,m){
    n=top=cnt=0;
    memset(e,0,sizeof e);
    memset(dfn,0,sizeof dfn);
    memset(cut,0,sizeof cut);
    memset(dcc,0,sizeof dcc);
    for(int a,b;m--;){
      cin>>a>>b;
      n=max({n,a,b}); //最大结点编号
      e[a].push_back(b);
      e[b].push_back(a);
    }
    
    for(root=1; root<=n; root++)if(!dfn[root])tarjan(root);
    
    int res=0; ULL s=1;
    for(int i=1; i<=cnt; i++){ //枚举vDCC
      int num=0; 
      for(int x:dcc[i])if(cut[x]) num++; //vDCC中的割点数
      if(num==0){ //0个割点
        if(dcc[i].size()==1) res++; //孤立点放一个出口
        else res+=2,s*=dcc[i].size()*(dcc[i].size()-1)/2; //放2个出口,方案数*=C(siz,2)
      }
      else if(num==1) //1个割点
        res++,s*=dcc[i].size()-1; //放1个出口,方案数*=(siz-1)
    }
    printf("Case %d: %d %llu\n",T++,res,s);
  }
}

 

posted @ 2026-05-21 14:06  董晓  阅读(71)  评论(0)    收藏  举报