2019ccpc秦皇岛 Forest Program(dfs+并查集)

传送门

题意:给一个n个点m条边的图,没有重边和自环,并且每条边最多被一个环覆盖,问把这个图变成森林,即 每个联通块都没有环 的图,有多少种方案。



思路:必须去掉的边是每个环中的某一条边,剩下的散边都是可以去掉,也可以不去掉,假如有k个环,每个环里面的边数是mi(1<=i<=k),那么答案就是
(2m1 -1) * (2m2 -1) * (2m3-1) * … * ( 2mk-1) * 2m0
其中,m0=m-m1-m2…-mk,即所有不在环中的边数和



刚开始不知道怎么求一个环里面有多少条边,,,听说别人都是树剖,LCA,然鹅我也不会,就学了一个dfs暴力大法,但是因为是无向图,所以我们得避免同一条边走两次的情况,所以要单另设一个数组来标记这条边是否已经走过。

代码

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
typedef long long ll;
const int M = 5e5+10, N= 3e5+10,mod = 998244353;
int e[M<<1],ne[M<<1],h[M<<1],w[M<<1],idx,cnt;
int vise[M],visv[N],pre[N],num[N],vis[N],ans[N];
int n,m;
void init()
{
	memset(h,-1,sizeof h);
	memset(num,0,sizeof num);
	memset(vis,0,sizeof vis);
	for(int i=1;i<=n;i++) pre[i]=i;
	idx=0;
}
void add(int a,int b,int c)
{
	e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
ll q_pow(ll a,int b)
{
	ll ans=1;
	while(b)
	{
		if(b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}
int find(int x)
{
	if(x==pre[x]) return x;
	return pre[x]=find(pre[x]);
}
void dfs(int x,int nume)  //x-当前结点编号,nume-当前共访问了多少条边
{
	for(int i=h[x];i!=-1;i=ne[i])
	{
		int j=e[i],id=w[i];
		if(vise[id]) continue;  //如果这条边已经访问过了
		vise[id]=1;
		//如果当前结点已经访问过了,那么存在一个环,环中边数为当前总边数-第一次走到该点时的边数
		if(visv[j]>=0)   
			ans[cnt++]=nume-visv[j];
		else
		{
			visv[j]=nume;
			dfs(j,nume+1);
		}
	}
}
int main()
{
	while(~scanf("%d%d",&n,&m))
	{
		init();
		for(int i=1;i<=m;i++)
		{
			int a,b;
			scanf("%d%d",&a,&b);
			add(a,b,i);add(b,a,i);
			int fa=find(a),fb=find(b);
			if(fa!=fb) num[fa]+=num[fb]+1,pre[fb]=fa;  //num数组记录联通块边数
			else num[fa]++;
		}
		ll res=1;
		for(int i=1;i<=n;i++)
		{
			int fi=find(i);
			if(!vis[fi])  //当前联通块没访问过
			{
				vis[fi]=1;
				cnt=0;
				memset(vise,0,sizeof vise);
				memset(visv,-1,sizeof visv);
				visv[fi]=0;
				dfs(fi,1);
				for(int j=0;j<cnt;j++)
				{
					res=res*(q_pow(2,ans[j])-1)%mod;
					num[fi]-=ans[j];
				}
				res=res*q_pow(2,num[fi])%mod;
			}
		}
		cout<<res<<endl;
	}
	return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值