题目链接
题意
- 就是给你一个仙人掌图(就是指一种没有重边自环的无向图,且每条边最多出现在一个简单环中,脑补一下是不是很像仙人掌),然后问你每条边可以选择保留或者是不保留,求最后形成的森林有多少种,定义当有两个任意的节点 u , v u,v u,v在一个森林中有边相连,在另一个森林中没有时,这两个森林就是不同的
题解
- 假设一个环中的边数为
k
k
k,那么这个环的方案数就是
2
k
−
1
2^k-1
2k−1,因为要去掉所有边都不删的情况,所以总方案数就等于
2
r
e
s
t
×
∏
i
=
1
c
n
t
(
2
s
i
z
e
[
i
]
−
1
)
2^{rest}\times \prod_{i=1}^{cnt}{(2^{size[i]}-1)}
2rest×i=1∏cnt(2size[i]−1)
其中 c n t cnt cnt表示总环数, s i z e [ i ] size[i] size[i]表示第 i i i个环的边数(=点数), r e s t = m − ∑ i = 1 c n t s i z e [ i ] rest=m-\sum_{i=1}^{cnt}{size[i]} rest=m−∑i=1cntsize[i],即剩下的边数 - 仙人掌图中的环两两之间没有公共边,但是可能有公共点,所以我们要找点双连通分量就行了
- 对于一条边两个点构成的点双连通分量也会被找出来,注意特判
代码
#pragma GCC optimize("3")
#include<bits/stdc++.h>
using namespace std;
const int maxn=3e5+10;
const int maxm=5e5+10;
const long long mod=998244353;
/*
sta:计算点双连通分量时,保留在当前BCC(Bioconnected Component)中的边
dfn:节点i的dfs序 low[i]:节点i在dfs树上指回的最小dfs序
dfs_clock:dfs计时器 tot:链式前向星总边数 cnt:当前栈(sta)中元素个数 bcc_cnt:记录bcc个数
is_cut[i]:节点i是否是割点
bcc[i]:存第i个点双连通分量的所有节点的vector
*/
namespace block{
int n,m,head[maxn],dfn[maxn],low[maxn],bccno[maxn],dfs_clock,tot,cnt,bcc_cnt;
bool is_cut[maxn];
vector<int> bcc[maxn];
struct ed {int u,v,w,next;}edge[2*maxm];
ed sta[maxn];
inline void clear_(int n) { //注意init操作包括清图
tot=dfs_clock=cnt=bcc_cnt=0;
for(int i=1;i<=n;i++) head[i]=dfn[i]=low[i]=bccno[i]=is_cut[i]=0;
}
inline void add_edge(int u,int v,int w) {
edge[++tot]=ed{u,v,w,head[u]};
head[u]=tot;
}
inline int BCC(int u,int fa) {
int lowu=dfn[u]=++dfs_clock,child=0;
for(int i=head[u];i;i=edge[i].next){
if(!dfn[edge[i].v]) {
sta[++cnt]=edge[i],child++;
int lowv=BCC(edge[i].v,u);
lowu=min(lowu,lowv);
if(lowv>=dfn[u]) {
is_cut[u]=true;
bcc[++bcc_cnt].clear();
for(;;) {
ed x=sta[cnt--];
if(bccno[x.u]!=bcc_cnt) {bcc[bcc_cnt].push_back(x.u);bccno[x.u]=bcc_cnt;}
if(bccno[x.v]!=bcc_cnt) {bcc[bcc_cnt].push_back(x.v);bccno[x.v]=bcc_cnt;}
if(x.u==u && x.v==edge[i].v) break;
}
}
}else if(dfn[edge[i].v]<dfn[u] && edge[i].v!=fa ){
sta[++cnt]=edge[i];
lowu=min(lowu,dfn[edge[i].v]);
}
}
if(!fa && child==1) is_cut[u]=0;
return low[u]=lowu;
}
//求出点双连通分量
inline void find_bcc() {
for(int i=1;i<=n;i++) if(!dfn[i]) BCC(i,0);
}
}
using namespace block;
long long quick_pow(long long a,long long b) {
assert(b>=0);
long long res=1;
while(b) {
if(b&1) res=res*a%mod;
a=a*a%mod;
b>>=1;
}
return res;
}
int main() {
while(~scanf("%d %d",&n,&m)) {
for(int i=1,u,v;i<=m;i++) {
scanf("%d %d",&u,&v);
add_edge(u,v,0);
add_edge(v,u,0);
}
find_bcc();
long long ans=1,k=0;
for(int i=1;i<=bcc_cnt;i++) if(bcc[i].size()>2) ans=ans*(quick_pow(2,bcc[i].size())-1)%mod,k+=bcc[i].size();
ans=ans*quick_pow(2,m-k)%mod;
printf("%lld\n",ans);
clear_(n);
}
}