[bzoj3812]主旋律

题目大意

问多少边的子集仍然是强联通的。

DP

设f[i]表示点集为i有多少边的子集强联通。
考虑补集转化,如果不是强联通,缩点后会形成一个DAG。用总的减去非法。
考虑容斥,枚举出度为0的点的点集j,那么如果包含奇数个强连通分量,则表示至少j个出度为0,容斥系数为负,否则容斥系数为正。
那么,我们可以设g[i]表示点集为i有多少边的子集形成奇数个互相之间没有边的强联通分量 ,h[i]表示点集为i有多少边的子集形成偶数个互相之间没有边的强联通分量。
g[i]=f[j]h[ij]
h[i]=f[j]g[ij]
为了不计重,j必须包含i中编号最小的点。
然后f也容易计算了。
f[i]=2i(h[j]g[j])2iji

#include<cstdio>
#include<algorithm>
#define fo(i,a,b) for(i=a;i<=b;i++)
#define lowbit(x) (x&-x)
using namespace std;
typedef long long ll;
const int maxn=15+10,mo=1000000007;
int cnt[maxn][33000],mi[300],id[33000];
int f[33000],g[33000],h[33000];
int i,j,k,l,t,n,m,tot,top,num,ans;
int main(){
    scanf("%d%d",&n,&m);
    fo(i,1,n) id[1<<(i-1)]=i;
    mi[0]=1;
    fo(i,1,m){
        mi[i]=(ll)mi[i-1]*2%mo;
        scanf("%d%d",&j,&k);
        cnt[j][1<<(k-1)]++;
    }
    fo(i,1,n)
        fo(j,1,(1<<n)-1)
            cnt[i][j]=cnt[i][lowbit(j)]+cnt[i][j-lowbit(j)];
    h[0]=1;
    fo(i,1,(1<<n)-1){
        if (i==lowbit(i)){
            f[i]=g[i]=1;
            h[i]=0;
            continue;
        }
        tot=0;
        /*fo(j,1,n)
            if (i&(1<<(j-1))) tot+=cnt[j][i];*/
        k=i;
        while (k){
            t=lowbit(k);
            tot+=cnt[id[t]][i];
            k-=t;
        }
        f[i]=mi[tot];
        j=i;
        while (1){
            j=(j-1)&i;
            if (!j) break;
            if (j&lowbit(i)){
                (g[i]+=(ll)f[j]*h[i-j]%mo);
                if (g[i]>=mo) g[i]-=mo;
                (h[i]+=(ll)f[j]*g[i-j]%mo);
                if (h[i]>=mo) h[i]-=mo;
            }
            tot=0;
            /*fo(k,1,n) 
                if ((i-j)&(1<<(k-1))) tot+=cnt[k][i];*/
            k=i-j;
            while (k){
                t=lowbit(k);
                tot+=cnt[id[t]][i];
                k-=t;
            }
            (f[i]-=(ll)g[j]*mi[tot]%mo)%=mo;
            (f[i]+=mo)%=mo;
            (f[i]+=(ll)h[j]*mi[tot]%mo);
            if (f[i]>=mo) f[i]-=mo;
        }
        (f[i]+=h[i]);
        if (f[i]>=mo) f[i]-=mo;
        (f[i]-=g[i])%=mo;
        (f[i]+=mo)%=mo;
        (g[i]+=f[i]);
        if (g[i]>=mo) g[i]-=mo;
    }
    ans=f[(1<<n)-1];
    printf("%d\n",ans);
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值