Tarjan 点双+割点+DFS【洛谷P3225】 [HNOI2012]矿场搭建

P3225 [HNOI2012]矿场搭建

题目描述

煤矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。

请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。

点双入门。。。 话说题意是真的晦涩。

一眼求割点,求完割点怎么办?

因为我们把割点去掉之后,图中会剩下一个一个的块,这些块就是点双。

那么限制工人逃跑的就是割点,所以我们在乎的是每个点双中有多少个割点。

那么我们就可以通过Tarjan给所有割点打上一个标记。

然后再去DFS出所有的点双,这个过程需要记录点双中的点和点双中的割点数。有些细节。

然后就可以愉快地分情况讨论了。

(一),当前点双内没有割点,那么这个点双是独立的也就是不和其他的块联通,那么对于这个点双内的点我们就需要建立两个救援出口,为什么呢,因为有一个炸了还有另外一个。所以\(ans1+=2\)

然后方案数通过乘法原理和组合数搞一搞就行了,也就是\(ans2*=C(tot,2)\)

(二),当前点双内只有一个割点,那么可以知道如果这个割点炸了我们就翻车了,所以我们可以继承(一)的思想,只是把这个已有的割点看成一个必然的出口,所以我们还需要建1个出口,方案数为\(ans2*=C(tot,1)\)

(三),当前点双内有两个割点,还是一样的思想,就会发现我们不用再建出口了,因为可以把两个割点看成两个出口,任意一个炸了我们还有另一个。

ok,那就可以开始愉快地写代码了喵喵喵。。。

code:

#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

#define int long long

const int wx=517;

inline int read(){
	int sum=0,f=1; char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar();}
	while(ch>='0'&&ch<='9'){sum=(sum<<1)+(sum<<3)+ch-'0'; ch=getchar();}
	return sum*f;
}

int n,m,ans1,ans2,num,sjc,tot,zmj,cnt;
int dfn[wx],head[wx],ok[wx],vis[wx],low[wx];

struct e{
	int nxt,to;
}edge[wx*2];

void add(int from,int to){
	edge[++num].nxt=head[from];
	edge[num].to=to;
	head[from]=num;
}

void Tarjan(int u,int f){
	dfn[u]=low[u]=++tot; int child=0;
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(v==f)continue;
		if(!dfn[v]){
			Tarjan(v,u);
			child++;
			low[u]=min(low[u],low[v]);
			if(low[v]>=dfn[u])ok[u]=1;
		}
		else if(dfn[v]<dfn[u]&&v!=f){
			low[u]=min(low[u],dfn[v]);
		}
	}
	if(child<=1&&f==-1)ok[u]=0;
}

void dfs(int u){
	vis[u]=tot; zmj++; 
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(vis[v]!=vis[u]&&ok[v])cnt++,vis[v]=tot;
		if(!vis[v]&&!ok[v])dfs(v);
	}
}

void clear(){
	num=0,tot=0;
	memset(head,0,sizeof head);
	memset(edge,0,sizeof edge);
	memset(vis,0,sizeof vis);
	memset(dfn,0,sizeof dfn);
	memset(ok,0,sizeof ok);
}

signed main(){
	while(1){
		m=read(); if(!m)break;
		n=0; sjc++; ans1=0; ans2=1;
		clear();
		for(int i=1;i<=m;i++){
			int x,y;
			x=read(); y=read();
			add(x,y); add(y,x); 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++){
			if(ok[i]||vis[i])continue;
			tot++; zmj=0,cnt=0; dfs(i);
			if(cnt==0)ans1+=2,ans2*=(zmj-1)*zmj/2;
			if(cnt==1)ans1++,ans2*=zmj;
		}
		printf("Case %lld: %lld %lld\n",sjc,ans1,ans2);
	}
	return 0;
}
posted @ 2018-11-05 11:38  _王小呆  阅读(166)  评论(0编辑  收藏  举报