題意:給一個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;
}