「HNOI 2012」矿场搭建

传送门


problem

给出一个无向图,你可以将其中若干个点设成特殊点,满足,删掉图中任意一个点后,其他点都与至少一个特殊点连通。

请求出至少需要多少个特殊点,以及在最小化特殊点数时的方案数

数据范围:边数 m ≤ 500 m\le500 m500


solution

感觉之前写的好水啊,重新写一下。

看到这种连通性问题,应该能自然而然地想到 Tarjan 算法。

对于一个点双,那么有这几种情况(设非割点数为 c n t cnt cnt):

  • 没有割点。那么任意选 2 2 2 个设为特殊点即可(不能只选 1 1 1 个,因为选的那个被毁就 g g gg gg 了),方案数是 c n t ( c n t − 1 ) 2 \frac{cnt(cnt-1)}2 2cnt(cnt1)
  • 1 1 1 个割点。任意选 1 1 1 个点即可,如果它被毁,还可以从割点走到另一个点双去。
  • 2 2 2 个及以上的割点。这时并不用设置特殊点,发现毁掉任意一个点,其他点都可以通过割点到其他点双。

然后按照乘法原理把每种的方案数算一算即可。

时间复杂度 O ( n ) O(n) O(n)


code

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1005;
int n,m,root,tot,num;
int dfn[N],low[N],cut[N],vis[N];
int t,first[N],v[N],nxt[N];
void add(int x,int y)  {nxt[++t]=first[x],first[x]=t,v[t]=y;}
void Max(int &x,int y)  {if(x<y)x=y;}
void Min(int &x,int y)  {if(x>y)x=y;}
void Clear(){
	n=t=tot=num=0;
	memset(first,0,sizeof(first));
	memset(dfn,0,sizeof(dfn));
	memset(low,0,sizeof(low));
	memset(vis,0,sizeof(vis));
	memset(cut,0,sizeof(cut));
}
void Tarjan(int x,int son=0){
	dfn[x]=low[x]=++tot;
	for(int i=first[x];i;i=nxt[i]){
		int to=v[i];
		if(!dfn[to]){
			son++,Tarjan(to),Min(low[x],low[to]);
			cut[x]|=(root==x&&son>1)|(root!=x&&low[to]>=dfn[x]);
		}
		else  Min(low[x],dfn[to]);
	}
}
int cnt_cut,cnt_pnt,ans1;
ll ans2;
void dfs(int x,int id){
	vis[x]=id,cnt_pnt++;
	for(int i=first[x];i;i=nxt[i]){
		int to=v[i];
		if(cut[to]&&vis[to]!=id)  vis[to]=id,cnt_cut++;
		if(!vis[to])  dfs(to,id);
	}
}
int main(){
	int Case=0;
	while((~scanf("%d",&m))&&m){
		Clear();
		for(int i=1,x,y;i<=m;++i){
			scanf("%d%d",&x,&y);
			add(x,y),add(y,x),Max(n,max(x,y));
		}
		for(int i=1;i<=n;++i)
			if(!dfn[i])  Tarjan(root=i);
		ans1=0,ans2=1;
		for(int i=1;i<=n;++i){
			if(cut[i]||vis[i])  continue;
			cnt_cut=cnt_pnt=0,dfs(i,++num);
			if(cnt_cut==0)  ans1+=2,ans2*=cnt_pnt*(cnt_pnt-1)/2;
			if(cnt_cut==1)  ans1+=1,ans2*=cnt_pnt;
		}
		printf("Case %d: %d %lld\n",++Case,ans1,ans2);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值