洛谷·[HNOI2012]矿场搭建

初见安~这里是传送门:洛谷P3225

题目描述

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

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

输入文件有若干组数据,每组数据的第一行是一个正整数 N(N<=500),表示工地的隧道数,接下来的 N 行每行是用空格隔开的两个整数 S 和 T,表示挖 S 与挖煤点 T 由隧道直接连接。输入数据以 0 结尾。

输入文件中有多少组数据,输出文件 output.txt 中就有多少行。每行对应一组输入数据的 结果。其中第 i 行以 Case i: 开始(注意大小写,Case 与 i 之间有空格,i 与:之间无空格,: 之后有空格),其后是用空格隔开的两个正整数,第一个正整数表示对于第 i 组输入数据至少需 要设置几个救援出口,第二个正整数表示对于第 i 组输入数据不同最少救援出口的设置方案总 数。输入数据保证答案小于 2^64。输出格式参照以下输入输出样例。

输入样例

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

输出样例

Case 1: 2 4
Case 2: 4 1

题解:

首先要说明的显而易见的一点:如果救援点塌了,那么这个点就废了。

我们再来看——有没有必要再设救援点,取决于这个点坍塌后会不会阻断这个图的连通性。一个能让这个图不连通的点,就是割点。(这是不是巧合呀?这是有道理的~)所以我们只需要考虑割点坍塌后这个图需要怎么设置救援点了。

我们再来通过样例分析救援点的设置与方法数的关系——很明显,有必须设的点,有可以有多种方法的点,而这些多种方法的点的数量之乘积就是方案数。不理解的话,可以手动画图模拟样例看看。

扫描二维码关注公众号,回复: 5770946 查看本文章

假设上面一头一尾的操作我们都明白了,那么最关键的——怎么知道这个割点划分开的区域里没有已经确定了要设置的救援点呢?

因为又同样涉及到对于找到割点后划分开来的连通块的操作,所以我们可以联系到求v-DCC的思路——当然要开个vec存一下也可以,这里就不必了。因为终归涉及到的都是遍历,所以从某种程度上说不存会更优一些。:)我们顺着这个思路走——在每个v-DCC中,可能会有割点存在,那就意味着这一个连通块必须要有一个救援点。但是细想就会发现——不一定。如果一个割点塌了,还有别的割点可以通向其他连通块。所以我们只需要判断只有一个割点的情况就可以了。那么没有割点的情况呢?任意设置两个点即可——确保塌了一个还有一个即可。就这么简单:)

不要觉得代码长,其实核心代码没多少,主要都是细节和套路。

#include<bits/stdc++.h>
#define maxn 505
using namespace std;//由范围可推断:不超过33个点 
long long m,cnt=0,ans1,ans2,root,n,ans,rec,num;
int vis[maxn];
struct edge 
{
	int to,nxt;
	edge(){}
	edge(int tt,int nn)
	{
		to=tt,nxt=nn;
	}
}e[maxn];

int head[maxn],k=0;
void add(int u,int v)
{
	e[k]=edge(v,head[u]);
	head[u]=k++;
}

int low[maxn],dfn[maxn],tot=0;
bool cut[maxn];
void tarjan(int x)
{
	dfn[x]=low[x]=++tot;
	int flag=0;
	for(register int i=head[x];~i;i=e[i].nxt)
	{
		int y=e[i].to;
		if(!dfn[y])
		{
			tarjan(y);
			low[x]=min(low[x],low[y]);
			if(dfn[x] <= low[y] && (x != root || ++flag > 1)) cut[x]=1;//找割点
		}
		else low[x]=min(low[x],dfn[y]);
	}
}


void dfs(int x)
{
	vis[x]=num;//vis存的是v-DCC的序号
	ans++;
	for(int i=head[x];~i;i=e[i].nxt)
	{
		int y=e[i].to;
		if(cut[y]&&vis[y] != num)//这个点不是在这个v-DCC里访问到的
		{
			rec ++;
			vis[y] = num;//更新
		}
		
		if(!vis[y]) dfs(y);//深优遍历
	}
}


void init()
{
	memset(head,-1,sizeof head);
	memset(vis,0,sizeof vis);
	memset(cut,0,sizeof cut);
	memset(dfn, 0, sizeof dfn);
	memset(low, 0, sizeof low);
	ans1=0,ans2=1;
	cnt++;
	num=0;
	n=0;
}

int a,b;
int main()
{
	while(cin>>m)
	{
		if(m==0) return 0;
		init();
		for(int i=1;i<=m;i++)
		{
			cin>>a>>b;
			add(a,b);
			add(b,a);
			if(a>n||b>n) n=max(a,b);
		}
		
		for(register int i=1;i<=n;i++)
		{
			if(!dfn[i]) root=i,tarjan(i);
		}
		
		for(register int i=1;i<=n;i++)
		{
			if(!vis[i]&&!cut[i])
			{
				rec=ans=0;//rec统计这个点双联通分量涉及到了多少个割点,ans为非割点点数
				num++;
				dfs(i);
				if(!rec)
				{
					ans1+= 2;
					ans2*= (ans-1) * ans / 2;//从任意非割点的地方选择两个点建立 
				}
				else if(rec == 1)
				{
					ans1++;
					ans2*= ans; //如结论2
				}//只需要判断涉及到0和1个割点即可,因为多个的话定有其他分量会处理。
			}
		}
		cout<<"Case "<<cnt<<": "<<ans1<<" "<<ans2<<endl;
	}
}

迎评:)

——End——

猜你喜欢

转载自blog.csdn.net/qq_43326267/article/details/88649811
今日推荐