CF557D Vitaly and Cycle 题解

前置

第一次独立做出来 *2000 的题,肯定是 cf 评分评高了。

题意简述

给定一个 n n n 个点 m m m 条边的无向图,问最少添加几条边使图中有奇环,且添加边的方案有多少种。

解题思路

不难发现最多加 3 3 3 条边。因为保证 n ≥ 3 n\geq 3 n3,所以你可以随机选 3 3 3 个点然后在它们之间加边,就有奇环了。

具体地说,我们分以下几种情况:

∙ \bullet m = 0 m=0 m=0

这时候必须加 3 3 3 条边,加边的方案数为 n ∗ ( n − 1 ) ∗ ( n − 2 ) 6 \frac{n*(n-1)*(n-2)}{6} 6n(n1)(n2)。实质就是从 n n n 个点中选三个点的方案数。

∙ \bullet 存在奇环

此时答案为 0 1。主要讲一下如何判断奇环。我们给 dfs 一个参数 l e n len len,走到一个点时把这个点的 n u m num num 值设成 l e n len len,下次再走到时我们判断是否走了奇数步即可(不理解的可以看看代码)。

∙ \bullet 存在一个连通块包含至少三个点

这个时候我们只需要连 1 1 1 条边。具体的,从连通块中找出距离为偶数且不相邻的点连边即可。注意这个时候我们连的边一定是在块内连,不可能将两个连通块连起来,因为将两个本不相连的连通块连起来不会产生环。

如何统计连边方案数?我们从连通块中任意一个点出发,对整个块黑白染色,连的这一条边要么是黑点和黑点,要么是白点和白点,直接 dfs 统计黑点白点数量然后计算即可。

∙ \bullet 其余情况

这个时候我们需要连 2 2 2 条边,方案数为 m × ( n − 2 ) m \times (n-2) m×(n2)。事实上有了前面几种情况铺垫,到这里时,已经保证连通块内点数不超过 2 2 2 了。也就是说每一条边都是孤立的!我们假设某条边的两个端点是 x , y x,y x,y,此时我们找到一个 z z z,从 x , y x,y x,y 分别向 z z z 连一条边即可。共 m m m 条边,对于每条边我们可以找到 n − 2 n-2 n2 个上述的 z z z 点,所以方案数就出来了。

代码示例

#include
using namespace std;
#define int long long
int n,m,vis[200010],cnt_0=0,cnt_1=0,ans=0,num[200010],flg=0;
vector<int> G[200010];
void dfs(int x,int len){
	if(vis[x]){
		if(len-num[x]>=3&&(len-num[x])%2){
			cout<<"0 1"<<endl;
			exit(0);
		}
		return;
	}
	vis[x]=1,num[x]=len;
	if(num[x]%2) cnt_1++;
	else cnt_0++;
	for(int v:G[x]) dfs(v,len+1);
}
signed main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++){
		int u,v;
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	if(m==0) return cout<<"3 "<<n*(n-1)*(n-2)/6<<endl,0;
	for(int i=1;i<=n;i++) if(!vis[i]) dfs(i,0);
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++){
		if(!vis[i]){
			cnt_0=cnt_1=0;
			dfs(i,0);
			if(cnt_0>1||cnt_1>1) flg=1;
			ans=ans+cnt_0*(cnt_0-1)/2;
			ans=ans+cnt_1*(cnt_1-1)/2;
		}
	}
	if(flg) cout<<"1 "<<ans<<endl;
	else cout<<"2 "<<m*(n-2)<<endl;
	return 0;
}

你可能感兴趣的:(CF/ATC题解,深度优先,算法)