bzoj5287 [Hnoi2018]毒瘤(树形dp+枚举+虚树)

求一张连通无向图的独立集个数。特点是边数-点数<=11.
首先如何求一棵树的独立集个数呢?我们可以树形dp:
f[x][0/1]表示x子树内不选/选根x的独立集个数。我们有转移:
f[x][0]=(f[y][0]+f[y][1]) f [ x ] [ 0 ] = ∏ ( f [ y ] [ 0 ] + f [ y ] [ 1 ] )
f[x][1]=f[y][0] f [ x ] [ 1 ] = ∏ f [ y ] [ 0 ]
如果多一条边呢?我们可以基环树dp。
就讨论一下非树边链接的两点的状态即可。
我们最多只有11条非树边,因此可以暴力枚举一下这11条非树边连接的两点的状态,只有(0,0),(0,1)(1,0)三种,但其实我们可以只讨论 x选y不选,x不选两种情况。因此复杂度就是 O(2mnn) O ( 2 m − n n )
考虑进一步优化,我们每次枚举非树边状态时,其实改动不是很大,只有最多 O(nm) O ( n − m ) 个点的初值变化了。我们可以为这些关键点建虚树,提前处理出虚边的转移系数。g[x][0/1]表示x子树没有关键点的儿子的贡献,即常数。k[x][0/1][0/1]表示x节点 状态是0或者1 他们对于下一个虚树节点的转移系数分别是多少。遇到不是虚树上的节点 就把他的系数加进去 遇到虚树节点 就重置。
然后每次只在虚树上dp即可。
复杂度 O(n+2mn(mn)) O ( n + 2 m − n ( m − n ) )

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define inf 0x3f3f3f3f
#define N 100010
#define mod 998244353
inline char gc(){
    static char buf[1<<16],*S,*T;
    if(S==T){T=(S=buf)+fread(buf,1,1<<16,stdin);if(T==S) return EOF;}
    return *S++;
}
inline int read(){
    int x=0,f=1;char ch=gc();
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=gc();}
    while(ch>='0'&&ch<='9') x=x*10+ch-'0',ch=gc();
    return x*f;
}
int n,m,h[N],num=0,dfn[N],u[N],v[N],tot=0,dfnum=0,sz[N],g[N][2],h1[N],num1=0;
bool mark[N],vis[N],ban[N][2];int bin[20],dp[N][2],ans=0;
struct edge{
    int to,next;
}data[N+10<<1],data1[100];
struct Icefox{
    int k0,k1;
    Icefox(){}
    Icefox(int _k0,int _k1){k0=_k0;k1=_k1;}
    friend Icefox operator+(Icefox a,Icefox b){
        return Icefox((a.k0+b.k0)%mod,(a.k1+b.k1)%mod);
    }void operator*=(int x){
        k0=(ll)k0*x%mod;k1=(ll)k1*x%mod;
    }
}k[N][2],tr[100][2];
inline void dfs1(int x,int Fa){
    dfn[x]=++dfnum;sz[x]=0;
    for(int i=h[x];i;i=data[i].next){
        int y=data[i].to;if(y==Fa) continue;
        if(!dfn[y]) dfs1(y,x),sz[x]+=sz[y];
        else if(dfn[y]<dfn[x]) u[++tot]=x,v[tot]=y,mark[x]=mark[y]=1;
    }mark[x]|=(sz[x]>=2);if(mark[x]) sz[x]=1;
}
inline void add(int x,int y,Icefox a,Icefox b){
    data1[++num1].to=y;data1[num1].next=h1[x];h1[x]=num1;
    tr[num1][0]=a;tr[num1][1]=b;
}
inline int dfs2(int x){
    vis[x]=1;g[x][0]=g[x][1]=1;int res=0;
    for(int i=h[x];i;i=data[i].next){
        int y=data[i].to;if(vis[y]) continue;
        int z=dfs2(y);
        if(!z) g[x][0]=(ll)g[x][0]*(g[y][0]+g[y][1])%mod,g[x][1]=(ll)g[x][1]*g[y][0]%mod;
        else{
            if(mark[x]) add(x,z,k[y][0]+k[y][1],k[y][0]);
            else k[x][0]=k[y][0]+k[y][1],k[x][1]=k[y][0];res=z;
        }
    }if(mark[x]) k[x][0]=Icefox(1,0),k[x][1]=Icefox(0,1),res=x;
    else k[x][0]*=g[x][0],k[x][1]*=g[x][1];
    return res;
}
inline void inc(int &x,int y){x+=y;x%=mod;}
inline void dfs3(int x){
    dp[x][0]=ban[x][0]?0:g[x][0];
    dp[x][1]=ban[x][1]?0:g[x][1];
    for(int i=h1[x];i;i=data1[i].next){
        int y=data1[i].to;dfs3(y);
        dp[x][0]=(ll)dp[x][0]*((ll)tr[i][0].k0*dp[y][0]%mod+(ll)tr[i][0].k1*dp[y][1]%mod)%mod;
        dp[x][1]=(ll)dp[x][1]*((ll)tr[i][1].k0*dp[y][0]%mod+(ll)tr[i][1].k1*dp[y][1]%mod)%mod;
    }
}
int main(){
//  freopen("a.in","r",stdin);
    n=read();m=read();
    while(m--){
        int x=read(),y=read();
        data[++num].to=y;data[num].next=h[x];h[x]=num;
        data[++num].to=x;data[num].next=h[y];h[y]=num;
    }dfs1(1,0);mark[1]=1;dfs2(1);bin[0]=1;
    for(int i=1;i<=tot;++i) bin[i]=bin[i-1]<<1;
    for(int i=1;i<=n;++i) sz[i]=-1;
    for(int s=0;s<bin[tot];++s){
        for(int i=1;i<=tot;++i){
            if(s&bin[i-1]) ban[v[i]][0]=1,ban[u[i]][1]=1;
            else ban[v[i]][1]=1;
        }dfs3(1);inc(ans,(dp[1][1]+dp[1][0])%mod);
        for(int i=1;i<=tot;++i){
            if(s&bin[i-1]) ban[v[i]][0]=0,ban[u[i]][1]=0;
            else ban[v[i]][1]=0;
        }
    }printf("%d\n",ans);return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值