[BZOJ2730]矿场搭建:割点

这道题虽然看着像是道模板题,实际上有很多要注意的特殊情况。

我们对坍塌的点进行分类讨论。如果坍塌的点不是割点,显然不会影响图的连通性,也就不会影响其他人的逃生。考虑最简单的情况:一张图没有割点。我们是不是只需要1个出口就够了?并非如此,因为可能坍塌的点就是出口...因此,我们需要再设1个出口作为备用,个数为2,方案数为\small \frac{n*(n-1)}{2}n为点数)。如果有割点怎么办?先来看一个最简单的例子:123。如果坍塌的点为1,我们需要在点23设置出口;如果坍塌的点为22为割点),我们需要在点13设置出口;如果坍塌的点为3,我们需要在点21设置出口。因此,最优方案为在点13​​​​​​​设置出口。显然,坍塌的点如果是割点,我们就需要更多的出口(因为图连通性被破坏),同时,我们把任何一个出口设在割点也都是不优的。为什么呢?如果坍塌的点不是割点,把出口设在哪个点都无所谓;如果坍塌的点是割点(同时也是我们设的出口),我们就需要在删去割点后形成的每个连通子图中再设一个出口。反过来想想,在删去割点后形成的每个连通子图中再设一个出口,不就是最优方案了吗?至于方案数,就是所有连通子图大小的乘积(简单的乘法原理)。然而,我们还有一种特殊情况没考虑到:如果一个连通子图与两个及以上的割点相连,这个连通子图内就不需要设置出口!为什么呢?如果一个连通子图与两个及以上的割点相连,那么无论删去哪个割点,这个连通子图内的人都能经过其它割点跑到别的连通子图的出口中,自然不用多设出口。完整代码如下。

#include<cstdio>
#include<cstring>
#define maxn 50010
#define r register
#define cmax(_x,_y) (_x>_y?_x:_y)
#define cmin(_x,_y) (_x<_y?_x:_y)
using namespace std;
int t,n,m,num,a,b,ans1,first[maxn],next[maxn<<1],to[maxn<<1];
int now,cnt,dfn[maxn],low[maxn],size[maxn],deg[maxn];
//now表示dfs序,cnt表示删去所有割点后的连通子图个数,deg表示一个与连通子图相连的割点个数
long long ans2;
bool cut[maxn],vis[maxn],cal[maxn];
//cal[i]表示割点i是否被一个连通子图计算过(防止同一个割点被同一个连通子图计算多次)
int read()
{
	r char ch=getchar();r int in=0;
	while(ch>'9'||ch<'0') ch=getchar();
	while(ch<='9'&&ch>='0') in=(in<<1)+(in<<3)+ch-'0',ch=getchar();
	return in;
}
void tarjan(r int x,r int flag)//tarjan算法求割点,flag用于特判1号点是不是割点
{
	dfn[x]=low[x]=++now;
	for(r int i=first[x];i;i=next[i])
		if(!dfn[to[i]])
		{
			tarjan(to[i],0);
			low[x]=cmin(low[x],low[to[i]]);
			if(low[to[i]]<dfn[x]) continue;
			if(++flag>1||x!=1) cut[x]=1;//如果1号点有两个及以上的子结点,它就是割点
		}
		else low[x]=cmin(low[x],dfn[to[i]]);
}
void dfs(r int x)
{
	vis[x]=1,size[cnt]++;
	for(r int i=first[x];i;i=next[i])
	{
		if(!vis[to[i]]&&!cut[to[i]]) dfs(to[i]);
		else if(cut[to[i]]&&!cal[to[i]]) deg[cnt]++,cal[to[i]]=1;
	}
}
int main()
{
	while(m=read(),m)
	{
		n=num=now=cnt=0;
		memset(to,0,sizeof(to));
		memset(next,0,sizeof(next));
		memset(first,0,sizeof(first));
		for(r int i=1;i<=m;i++)
		{
			a=read(),b=read();
			n=cmax(n,cmax(a,b));
			to[++num]=b,next[num]=first[a],first[a]=num;
			to[++num]=a,next[num]=first[b],first[b]=num;
		}
		memset(dfn,0,sizeof(dfn));//初始化真麻烦
		memset(low,0,sizeof(low));
		memset(cut,0,sizeof(cut));
		memset(vis,0,sizeof(vis));
		memset(deg,0,sizeof(deg));
		memset(size,0,sizeof(size));
		tarjan(1,0),ans1=0,ans2=1;
		for(r int i=1;i<=n;i++)
			if(!vis[i]&&!cut[i])
			{
				memset(cal,0,sizeof(cal)),cnt++,dfs(i);//每次dfs都要初始化cal数组
				if(deg[cnt]<2) ans1++,ans2=1ll*ans2*size[cnt];
				//只有相连的割点数不超过2,才计入答案
			}
		if(cnt==1)	printf("Case %d: 2 %lld\n",++t,1ll*n*(n-1)/2);
		else printf("Case %d: %d %lld\n",++t,ans1,ans2);
		//如果不存在割点(整张图是一个点双连通分量)或删去所有割点后只剩下1个连通子图,需要特判
	}
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值