bzoj 3812: 主旋律

题意:

问n个点的生成子图是强连通的方案数。

题解:

神题,纠结了我几天。
先贴题解链orz
这题用容斥,即总的减不合法的。
首先一种暴力点的做法,因为不是强连通的话缩点后一定是>1个点的 DAG D A G ,所以可以枚举强连通分量,然后算出当前点集的生成子图的方案数 F(S) F ( S )
因为 DAG D A G 中一定有一些点出度为0,所以可以枚举那些点。

F(S)=TS,Tϕ(1)|T|12ways(ST,T)F(ST)(4) (4) F ( S ) = ∑ T ∈ S , T ≠ ϕ ( − 1 ) | T | − 1 2 w a y s ( S − T , T ) F ( S − T )

其中 ways(S,T) w a y s ( S , T ) 表示点集 S S 到点集T的边数。
因不能保证剩下的点中没有出度为0的点,且 TS,Tϕ(1)|T|1=1 ∑ T ∈ S , T ≠ ϕ ( − 1 ) | T | − 1 = 1 ,所以可以容斥。
然而这么做肯定会T。
其实枚举强连通分量只是为了得到容斥系数,换个角度想,我们可以算出一个东西 g(S) g ( S )
表示将点集 S S 分成奇数个强连通分量的方案数-偶数个强连通分量的方案数。
再设f(S)是点集 S S 的生成子图强连通方案数(就是题目要求的东西)
(5)g(S)=f(S)TS,Tϕ,uTf(T)g(ST)

这个挺好理解不解释,那个减号是因为多了一个强联通分量要取反。
然后推 f f
(6)f(S)=2h(S)TS,Tϕ2ways(ST,T)+h(ST)g(T)

其中 h(S) h ( S ) 表示点集 S S 的边数。
因为g已经容斥过了,这个时候就不是严格枚举点,所以剩下的点就可以任意连边没有限制。
好像 f,g f , g 会互相调用,其实当 S=T S = T g(S) g ( S ) 不包含这 f(S) f ( S ) 部分。
code:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#define LL long long
using namespace std;
const int mod=1000000007;
int n,m;
LL pow[225],f[1<<15],g[1<<15],h[1<<15],p[1<<15];
int bitcnt[1<<15],e_in[1<<15],e_out[1<<15];
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int x,y;scanf("%d %d",&x,&y);
        x=1<<(x-1);y=1<<(y-1);
        e_in[y]|=x;e_out[x]|=y;
    }
    pow[0]=1;
    for(int i=1;i<=n*n;i++) pow[i]=pow[i-1]*2%mod;
    bitcnt[0]=0;
    for(int i=1;i<(1<<n);i++)
        bitcnt[i]=bitcnt[i-(i&-i)]+1;
    for(int s=1;s<(1<<n);s++)
    {
        int u=s&-s,t=s^u;
        for(int i=t;i;i=(i-1)&t)
            g[s]=(g[s]-f[s^i]*g[i])%mod;
        h[s]=h[t]+bitcnt[e_in[u]&t]+bitcnt[e_out[u]&t];
        f[s]=pow[h[s]];
        for(int i=s;i;i=(i-1)&s)
        {
            if(i!=s)
            {
                u=(i^s)&-(i^s);
                p[i]=p[i^u]+bitcnt[e_out[u]&i]-bitcnt[e_in[u]&(s^i)];
            }
            else p[i]=0;
            f[s]=(f[s]-pow[p[i]+h[s^i]]*g[i])%mod;
        }
        g[s]=(g[s]+f[s])%mod;
    }
    printf("%lld",(f[(1<<n)-1]+mod)%mod);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值