题目描述:
定义沙漠是满足如下性质的图: 没有重边和自环,且图中任意一条边至多属于一个简单环。 给定一片沙漠,从中删去任意多条边,使得删去边之后的图是一个森林,求有多少种方法,答案对 998244353 取模。
这里必须吐槽一下…
题目描述很硬核:沙漠里有很多的仙人掌,把仙人掌修剪成树,就绿化了沙漠(笑)
看到这里就已经懵了?没关系,你只是不知道「仙人掌图」,不妨认识一下?
思路:
树中不能有环,所以要将「沙漠」删边变成「森林」,就是将每一个环至少删掉一条边,而不在环中的边则删不删都可以(森林中的树不要求连通)
重点在于环的查找:我们可以DFS记录边的深度,一旦从较深DFS到较浅,就说明找到了一个环
DFS的过程中我们有以下几种情况需要处理:
- DFS到了自己的父亲——continue;
- DFS到比自己浅的点——找到了一个环,环中的边数即为两点深度差+1;
- DFS到比自己深的店——访问到了已经统计过的环,不用管;
- DFS到未标记深度的点——一个新的点,将此点的深度标记为父亲的深度+1,并从此点开始DFS;
值得注意的一点在于情况4的处理,当我们遇到一个新的点并标记他的深度之后,我们需要从此点开始DFS,因为题目中并没有规定「沙漠」是一个连通的图(只有一株仙人掌的沙漠还叫沙漠?)
代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<vector>
#define ll long long
using namespace std;
const int maxn=300010;
const ll mod=998244353;
int vis[maxn];
vector<int>map[maxn];
int n,m;
int ans,num;
ll qp(ll a,ll b){
ll res=1;
while(b>0){
if(b%2)
res=(res*a)%mod;
a=(a*a)%mod;
b/=2;
}
return res;
}
void dfs(int u,int fa){
vis[u]=vis[fa]+1;
for(int i=0;i<map[u].size();i++){
int v=map[u][i];
if(fa==v) continue;
if(vis[v]==0)
dfs(v,u);
else if(vis[u]>vis[v]){
int temp=vis[u]-vis[v]+1;
num+=temp;
ans=(ans%mod*(qp(2,temp)-1)%mod)%mod;
}
}
}
int main(){
scanf("%d%d",&n,&m);
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;i++) map[i].clear();
ans=1,num=0;
for(int i=1;i<=m;i++){
int x,y;
scanf("%d%d",&x,&y);
map[x].push_back(y);
map[y].push_back(x);
}
for(int i=1;i<=n;i++){
if(!vis[i])
dfs(i,0);
}
ans=(ans%mod*(qp(2,m-num))%mod)%mod;
cout<<ans<<endl;
return 0;
}