bzoj 3812: 主旋律 状压dp+容斥原理

题意

响应主旋律的号召,大家决定让这个班级充满爱,现在班级里面有 n 个男生。
如果 a 爱着 b,那么就相当于 a 和 b 之间有一条 a→b 的有向边。如果这 n 个点的图是强联通的,那么就认为这个班级是充满爱的。
不幸的是,有一些不好的事情发生了,现在每一条边都可能被摧毁。我作为爱的使者,想知道有多少种摧毁的方式,使得这个班级任然充满爱呢?(说人话就是有多少边的子集删去之后整个图仍然强联通。)
n≤15,0≤m≤n(n−1),答案对1e9+7取模。

分析

直接求显然不好求,考虑容斥。设 f[S] 表示 S 中的点强连通的方案,g[S]表示 S 中的点缩点后形成了奇数个强连通分量的方案,h[S]表示 S 中的点缩点后形成了偶数个强连通分量的方案。
不难注意到如果一个方案不合法,那么它缩点后一定会出现若干个出度为0的点,考虑枚举这些出度为0的点由哪些点组成。
先考虑g[S] h[S] 要如何转移。
枚举 S 中编号最小的点所在的连通块,可以得到
g[S]=f[i]h[Si]
h[S]=f[i]g[Si]
如果 g[i] f[S] 作贡献,表明至少有奇数个点出度为0,所以其容斥系数为-1,同理 h[S] 的容斥系数为1,可以得到
f[S]=2S+(h[i]g[i])2(Si)i
可以通过预处理一些东西,使得转移的复杂度达到 O(3n)

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;

const int N=20;
const int MOD=1000000007;

int n,m,bin[405],map[N],f[40005],g[40005],h[40005],sub[40005],w[40005],cnt[40005];

int read()
{
    int x=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}

int lowbit(int x) {return x&(-x);}

void updata(int &x,int y)
{
    x+=y;x-=x>=MOD?MOD:0;
}

int main()
{
    n=read();m=read();
    bin[0]=1;
    for (int i=1;i<=max(n,m);i++) bin[i]=bin[i-1]*2,bin[i]-=bin[i]>=MOD?MOD:0;
    for (int i=0;i<bin[n];i++) cnt[i]=cnt[i>>1]+(i&1);
    for (int i=1;i<=m;i++)
    {
        int x=read()-1,y=read()-1;
        map[x]|=bin[y];
    }
    for (int s=1;s<bin[n];s++)
    {
        if (s==lowbit(s)) {f[s]=g[s]=1;continue;}
        int top=0;
        for (int i=s&(s-1);i;i=s&(i-1)) sub[++top]=i;
        for (int i=0;i<n;i++) if (s&bin[i]) w[bin[i]]=cnt[map[i]&s];
        for (int i=top;i>=1;i--) w[sub[i]]=w[lowbit(sub[i])]+w[sub[i]^lowbit(sub[i])];
        w[s]=w[lowbit(s)]+w[s^lowbit(s)];f[s]=bin[w[s]];
        for (int i=1;i<=top;i++)
        {
            updata(f[s],(LL)(h[sub[i]]+MOD-g[sub[i]])*bin[w[s-sub[i]]]%MOD);
            if (!(sub[i]&lowbit(s))) continue;
            updata(g[s],(LL)f[sub[i]]*h[s-sub[i]]%MOD);
            updata(h[s],(LL)f[sub[i]]*g[s-sub[i]]%MOD);
        }
        updata(f[s],(h[s]+MOD-g[s])%MOD);
        updata(g[s],f[s]);
        w[s]=0;
        for (int i=1;i<=top;i++) w[sub[i]]=0;
    }
    printf("%d",f[bin[n]-1]);
    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值