题目描述
煤矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。
请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。
题目解析
tarjan找双联通分量,找割点。
在一个分量里有 ≥2 个割点,这个分量就怎么都能跑出去,不需要建出口。
在一个分量里有 1 个割点,这个分量就要防止割点塌了,需要建1个出口。
在一个分量里没有割点,说明它不和别的分量连通,为了防止出口塌掉,要建两个出口。
看起来有些坑,值得注意的是,一个点只会在一个强连通分量,但可能同时处于多个双联通分量
Code
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int MAXN = 500 + 5; struct Edge { int nxt; int to; } l[MAXN<<1]; int n,m,res,T; int tot,stamp; int head[MAXN],cnt; int low[MAXN],dfn[MAXN]; int sum,num; int root,deg; int vis[MAXN],col[MAXN]; bool cut[MAXN]; long long ans1,ans2 = 1; void tarjan(int x,int from) { dfn[x] = low[x] = ++stamp; for(int i = head[x];i;i = l[i].nxt) { if(!dfn[l[i].to]) { tarjan(l[i].to,x); low[x] = min(low[x],low[l[i].to]); if(low[l[i].to] >= dfn[x]) { if(x == root) deg++; else cut[x] = true; } } else if(l[i].to != from) low[x] = min(low[x],dfn[l[i].to]); } return; } void dfs(int x) { vis[x] = tot; if(cut[x]) return; sum++; for(int i = head[x];i;i = l[i].nxt) { if(cut[l[i].to] && vis[l[i].to] != tot) num++,vis[l[i].to] = tot; if(!vis[l[i].to]) dfs(l[i].to); } } inline void add(int x,int y) { cnt++; l[cnt].nxt = head[x]; l[cnt].to = y; head[x] = cnt; return; } inline void clean() { memset(low,0,sizeof(low)); memset(dfn,0,sizeof(dfn)); memset(head,0,sizeof(head)); memset(col,0,sizeof(col)); memset(cut,0,sizeof(cut)); memset(vis,0,sizeof(vis)); num = sum = 0; ans1 = n = tot = stamp = cnt = 0; ans2 = 1; } int main() { while(~scanf("%d",&m) && m) { clean(); int x,y; for(int i = 1;i <= m;i++) { scanf("%d%d",&x,&y); n = max(n,max(x,y)); add(x,y),add(y,x); } for(int i = 1;i <= n;i++) { if(!dfn[i]) { deg = 0; tarjan(root = i,0); if(deg >= 2) cut[root] = true; } } for(int i = 1;i <= n;i++) { if(!vis[i] && !cut[i]) { tot++; sum = num = 0; dfs(i); if(!num && sum > 1) ans1 += 2, ans2 *= sum*(sum-1)/2; else if(num == 1) ans1++, ans2 *= sum; } } printf("Case %d: %lld %lld ",++T,ans1,ans2); } return 0; }