[HNOI2012]矿场搭建 (tarjan求点双)

链接:https://ac.nowcoder.com/acm/problem/20099
来源:牛客网

题目描述
煤矿工地可以看成是由隧道连接挖煤点组成的无向图。为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处。
于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口。
请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数。
输入描述:
输入文件有若干组数据,每组数据的第一行是一个正整数 N(N ≤ 500),表示工地的隧道数,
接下来的N行每行是用空格隔开的两个整数S和T,表示挖S与挖煤点T由隧道直接连接。
输入数据以 0 结尾。
输出描述:
输入文件中有多少组数据,输出文件 output.txt 中就有多少行。
每行对应一组输入数据的结果。
其中第 i 行以 Case i: 开始(注意大小写,Case 与 i 之间有空格,i 与:之间无空格,: 之后有空格),其后是用空格隔开的两个正整数,
第一个正整数表示对于第 i 组输入数据至少需 要设置几个救援出口,
第二个正整数表示对于第 i 组输入数据不同最少救援出口的设置方案总数。
输入数据保证答案小于 2^64。输出格式参照以下输入输出样例。
示例1
输入
复制
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

分析:

在无向图中,如果一个双连通分量中没有原图的割点,那么需要设置2个出口,答案是size*(size-1)/2,如果有一个割点,那么需要设置一个出口,因为当割点之外的点删除,其他点可通过割点到其他的点双,为防止割点删除,所以需要设置一个出口,答案是size-1,如果有大于等于2个割点,那么不需要设置出口,因为无论那个点删除,其他点都可以通过割点到其他的点双中去,而且这种情况下总有点双是只有一个割点的,所以最后答案不会是0。
一种做法是用tarjan求割点,然后dfs求联通块。
另一种是直接用tarjan求点双,然后计算每个点双中割点的个数
下面的代码是第二种做法

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e3+10;
vector<int> g[N],t[N];
int n,m;
int dfn[N],low[N],num,s[N],is[N],top,cnt;
void tarjan(int x,int y)
{
	dfn[x]=low[x]=++num;
	s[++top]=x;
	int son=0;
	for(int i=0;i<g[x].size();i++)
	{
		int v=g[x][i];
		if(!dfn[v])
		{
			tarjan(v,x);
			low[x]=min(low[x],low[v]);
			son++;
			if((y==-1&&son>1)||(y!=-1&&dfn[x]<=low[v])) is[x]=1;
			if(low[v]>=dfn[x])
			{
			    t[++cnt].clear();
			    /*t[++cnt].push_back(x);
				while(s[top]!=x)
				{
					t[cnt].push_back(s[top--]);
				}*/  //直接把x后面的所有点全部弹出栈是不对的,只有40分 
				do
				{
					t[cnt].push_back(s[top--]);
				}while(s[top+1]!=v);
				/*
				因为有可能v之前的兄弟节点u有一条返祖的边,
				导致low[u]<dfn[x],u还在栈中,所以只需要把y及y之后的点弹出栈 
				*/ 
				t[cnt].push_back(x);//x要记录到点双中,但是不弹出栈 
			}
		}
		else if(dfn[v]<dfn[x]&&v!=y) low[x]=min(low[x],dfn[v]);
	}
	if(y==-1&&son==0) //只有一个点的情况 
	{
		t[++cnt].push_back(x);
	} 
}
int main()
{
	int z=0;
	while(scanf("%d",&n)!=EOF)//n只是边的数量,不是点的数量 
	{
		z++;
		if(n==0) break;
		cnt=num=top=0,m=1;
		int a,b;
		memset(dfn,0,sizeof(dfn));
		memset(low,0,sizeof(low));
		memset(is,0,sizeof(is));
		for(int i=1;i<=N;i++)
		{
			g[i].clear();
		}
		for(int i=1;i<=n;i++)
		{
			scanf("%d%d",&a,&b);
			g[a].push_back(b);
			g[b].push_back(a);
			m=max(m,a),m=max(m,b);
		}
		for(int i=1;i<=m;i++)
		{
			if(!dfn[i]) tarjan(i,-1);
		}
		ll ans=1;
		int k=0;
		for(int i=1;i<=cnt;i++)
		{
			int sum=0;
			for(int j=0;j<t[i].size();j++)
			{
				if(is[t[i][j]]) sum++;
			}
			ll p=t[i].size();
			if(sum==0) 
			{
				k+=2;
				if(p>2)
				ans=ans*p*(p-1)/2;
			}
			else if(sum==1) 
			{
				k+=1;
				if(p-1>1)
				ans=ans*(p-1);
			}
		}
		printf("Case %d: %d %lld\n",z,k,ans);
	}
	return 0;
 } 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值