「2019 CCPC秦皇岛站 F」Forest Program【点双连通分量】

题目链接

题意

  • 就是给你一个仙人掌图(就是指一种没有重边自环的无向图,且每条边最多出现在一个简单环中,脑补一下是不是很像仙人掌),然后问你每条边可以选择保留或者是不保留,求最后形成的森林有多少种,定义当有两个任意的节点 u , v u,v u,v在一个森林中有边相连,在另一个森林中没有时,这两个森林就是不同的

题解

  • 假设一个环中的边数为 k k k,那么这个环的方案数就是 2 k − 1 2^k-1 2k1,因为要去掉所有边都不删的情况,所以总方案数就等于 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=1cnt(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=mi=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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值