bzoj 3812: 主旋律(容斥原理+DP)

题目描述

传送门

题目大意:有多少边的子集删去之后整个图仍然强联通

题解

直接求强联通的子图不是很好求,所以我们考虑用子图的个数-不强联通的子图个数。
不强联通的子图有什么特点呢?将强连通分量缩点后会形成一个节点数>=2的DAG。
一个DAG肯定由一些没有出度的点构成,那么我们可以考虑枚举没有出度的点的子集T。那么S-T中的边和S-T 到T之间的边也可以随便连。但是我们这样连出来不能保证S-T中没有出度为0的点(或者强联通分量)。而且有可能会重复计算。比如说两个出度为0的点u,v,枚举T={u}的和T={v}时都会计算u,v。
那么我们就可以进行容斥求解。 f(s) 表示s构成DAG的方案数。

f(S)=TS,T!=0(1)|T|12way(ST,T)+h(ST)f(T)

其中 way(ST,T)STTh(ST)ST
我们维护 g(t) 表示i集合的点缩点后成为奇数个彼此没有边的点的方案数, c(t) 表示i集合的点缩点后成为偶数个彼此没有边的点的方案数,重新定义 f(t) 表示t是强联通子图的方案数。
g(t)=f(j)c(tj)
c(t)=f(j)g(tj)
那么递推的式子就是
f(s)=2h(s)+(c(j)g(j))2way(sj,j)+h(sj)

代码

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdio>
#define LL long long 
#define N 16
#define p 1000000007
using namespace std;
LL cnt[N][1<<16],mi[403],h[1<<16],g[1<<16],c[1<<16],f[1<<16];
int n,m;
int main()
{
    freopen("a.in","r",stdin);
    scanf("%d%d",&n,&m);
    mi[0]=1;
    for (int i=1;i<=300;i++) mi[i]=mi[i-1]*2%p;
    for (int i=1;i<=m;i++) {
        int x,y; scanf("%d%d",&x,&y);
        x--; y--; cnt[x][1<<y]++;
    }
    for (int i=0;i<(1<<n);i++) 
     for (int j=0;j<n;j++) {
        int t=i&(-i);
        cnt[j][i]=(cnt[j][t]+cnt[j][i-t])%p;
     }
    for (int i=0;i<(1<<n);i++)
     for (int j=0;j<n;j++)
      if (i&(1<<j)) h[i]=(h[i]+cnt[j][i])%p;
    //for (int i=0;i<(1<<n);i++) cout<<h[i]<<" ";
    //cout<<endl;
    c[0]=1;
    for (int i=0;i<(1<<n);i++) {
        if (i==(i&(-i))) {
            f[i]=g[i]=1;
            continue;;
        }
        f[i]=mi[h[i]];
        for (int j=i&(i-1);j;j=i&(j-1)) {
            int t=i-j; int tmp=0;
            for (int k=0;k<n;k++)
             if (t&(1<<k)) tmp=(tmp+cnt[k][i])%p;
            f[i]=(f[i]+mi[tmp]*(c[j]-g[j]))%p;
            if (j&(i&(-i))) {
                c[i]=(c[i]+f[j]*g[t])%p;
                g[i]=(g[i]+f[j]*c[t])%p;
            }
        }
        f[i]=(f[i]+c[i])%p; f[i]=(f[i]-g[i])%p;
        g[i]=(g[i]+f[i])%p;
    }
    int t=(1<<n)-1;
    f[t]=(f[t]%p+p)%p;
    printf("%lld\n",f[t]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值