D163 vDCC 缩点 Tarjan 算法 P3225 [HNOI2012] 矿场搭建
D163 vDCC 缩点 Tarjan 算法 P3225 [HNOI2012] 矿场搭建_哔哩哔哩_bilibili
地下无向图(不一定连通),在某些点上设置达到地面的出口,问最少设置几个出口,使得任意一点坍塌,其余点都可以走到出口
思路
参考: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); } }
浙公网安备 33010602011771号