【BZOJ-2730】矿场搭建 Tarjan 双连通分量
2730: [HNOI2012]矿场搭建
Time Limit: 10 Sec Memory Limit: 128 MBSubmit: 1602 Solved: 751
[Submit][Status][Discuss]
Description
煤矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。
Input
输入文件有若干组数据,每组数据的第一行是一个正整数 N(N≤500),表示工地的隧道数,接下来的 N 行每行是用空格隔开的两个整数 S 和 T,表示挖 S 与挖煤点 T 由隧道直接连接。输入数据以 0 结尾。
Output
输入文件中有多少组数据,输出文件 output.txt 中就有多少行。每行对应一组输入数据的 结果。其中第 i 行以 Case i: 开始(注意大小写,Case 与 i 之间有空格,i 与:之间无空格,: 之后有空格),其后是用空格隔开的两个正整数,第一个正整数表示对于第 i 组输入数据至少需 要设置几个救援出口,第二个正整数表示对于第 i 组输入数据不同最少救援出口的设置方案总 数。输入数据保证答案小于 2^64。输出格式参照以下输入输出样例。
Sample Input
9
1 3
4 1
3 5
1 2
2 6
1 5
6 3
1 6
3 2
6
1 2
1 3
2 4
2 5
3 6
3 7
0
1 3
4 1
3 5
1 2
2 6
1 5
6 3
1 6
3 2
6
1 2
1 3
2 4
2 5
3 6
3 7
0
Sample Output
Case 1: 2 4
Case 2: 4 1
Case 2: 4 1
HINT
Case 1 的四组解分别是(2,4),(3,4),(4,5),(4,6);
Case 2 的一组解为(4,5,6,7)。
Source
Solution
对于删除一个点,其余点要有出路,显然是和割点有关,那么我们求出所有割点,以及双连通分量。
对于一个双联通分量中,如果我们只删除一个割点或非割点,那么如果还有其余割点能使其余点到关键点,那么显然是不需要额外考虑的;如果没有其余的割点令我们到关键点,那么我们需要新建额外的关键点
分情况讨论,如果一个双联通分量里,有两个及以上割点,那么这个双联通分量里面是不需要额外建的
如果一个双联通分量里只有一个或没有割点,那么我们只需要再建一个即可。
至于方案数,可以利用乘法原理,我们把每个连通分量里的每个可以用来建成关键点的点用乘法统计起来即可
Code
#include<iostream> #include<cstdio> #include<cmath> #include<cstring> #include<algorithm> #include<vector> using namespace std; #define LL long long inline int read() { int x=0,f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while (ch>='0' && ch<='9') {x=x*10+ch-'0'; ch=getchar();} return x*f; } #define MAXM 1010 #define MAXN 80010 int N,M,tot; LL sum; struct EdgeNode{int next,to,from;}edge[MAXM<<1]; int head[MAXN],cnt=1; void AddEdge(int u,int v) {cnt++; edge[cnt].next=head[u]; head[u]=cnt; edge[cnt].to=v;} void InsertEdge(int u,int v) {AddEdge(u,v); AddEdge(v,u);} #define Pa pair<int,int> vector<int>BCC[MAXN]; Pa st[MAXM]; int top; int dfn[MAXN],low[MAXN],dfsn,cut[MAXN],bcc,belong[MAXN]; void Tarjan(int now,int last) { dfn[now]=low[now]=++dfsn; int son=0; for (int i=head[now]; i; i=edge[i].next) if (!dfn[edge[i].to]) { st[++top]=make_pair(now,edge[i].to); son++; Tarjan(edge[i].to,now); low[now]=min(low[now],low[edge[i].to]); if (dfn[now]<=low[edge[i].to]) { cut[now]=1; bcc++; BCC[bcc].clear(); int tnow=-1,tto=-1; while (1) { tnow=st[top].first,tto=st[top].second; top--; if (belong[tnow]!=bcc) BCC[bcc].push_back(tnow),belong[tnow]=bcc; if (belong[tto]!=bcc) BCC[bcc].push_back(tto),belong[tto]=bcc; if (tnow==now && tto==edge[i].to) break; } } } else if (dfn[edge[i].to]<dfn[now] && edge[i].to!=last) st[++top]=make_pair(now,edge[i].to),low[now]=min(low[now],dfn[edge[i].to]); if (last<0 && son==1) cut[now]=0; } int main() { int cas=0; while (scanf("%d",&M)) { if (M==0) break; N=0; cnt=1; memset(head,0,sizeof(head)); top=0; bcc=0; memset(st,0,sizeof(st)); memset(dfn,0,sizeof(dfn)); memset(low,0,sizeof(low)); bcc=0,dfsn=0; memset(cut,0,sizeof(cut)); memset(belong,0,sizeof(belong)); for (int x,y,i=1; i<=M; i++) x=read(),y=read(),InsertEdge(x,y),N=max(N,max(x,y)); for (int i=1; i<=N; i++) if (!dfn[i]) Tarjan(i,-1); // for (int i=1; i<=N; i++) printf("%d %d %d\n",dfn[i],belong[i],cut[i]); tot=0,sum=1; // printf("%d\n",bcc); // for (int i=1; i<=bcc; i++) printf("%d ",BCC[i].size()); puts(""); for (int i=1,num=0,sz=BCC[i].size()-1; i<=bcc; i++,num=0,sz=BCC[i].size()-1) { // printf("%d %d %I64d\n",num,tot,sum); for (int j=0; j<=sz; j++) if (cut[BCC[i][j]]) num++; if (num==1) tot++,sum*=(LL)(BCC[i].size()-num); if (bcc==1) {tot=2; sum=(LL)BCC[1].size()*(BCC[1].size()-1)/2; break;} } printf("Case %d: %d %lld\n",++cas,tot,sum); } return 0; }
——It's a lonely path. Don't make it any lonelier than it has to be.