题意:给一个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;
}